| // Copyright 2020 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/euicc.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/constants/ash_features.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chromeos/ash/components/dbus/hermes/hermes_euicc_client.h" |
| #include "chromeos/ash/components/dbus/hermes/hermes_profile_client.h" |
| #include "chromeos/ash/components/dbus/shill/shill_device_client.h" |
| #include "chromeos/ash/components/network/cellular_utils.h" |
| #include "chromeos/ash/components/network/fake_network_connection_handler.h" |
| #include "chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h" |
| #include "chromeos/ash/components/network/network_type_pattern.h" |
| #include "chromeos/ash/components/network/test_cellular_esim_profile_handler.h" |
| #include "chromeos/ash/services/cellular_setup/esim_test_base.h" |
| #include "chromeos/ash/services/cellular_setup/esim_test_utils.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" |
| |
| namespace ash::cellular_setup { |
| |
| namespace { |
| |
| using InstallResultPair = std::pair<mojom::ProfileInstallResult, |
| mojo::PendingRemote<mojom::ESimProfile>>; |
| |
| mojom::ESimOperationResult RefreshInstalledProfiles( |
| mojo::Remote<mojom::Euicc>& euicc) { |
| mojom::ESimOperationResult result; |
| base::RunLoop run_loop; |
| euicc->RefreshInstalledProfiles(base::BindOnce( |
| [](mojom::ESimOperationResult* out, base::OnceClosure quit_closure, |
| mojom::ESimOperationResult result) { |
| *out = result; |
| std::move(quit_closure).Run(); |
| }, |
| &result, run_loop.QuitClosure())); |
| run_loop.Run(); |
| return result; |
| } |
| |
| } // namespace |
| |
| class EuiccTest : public ESimTestBase { |
| public: |
| EuiccTest() = default; |
| EuiccTest(const EuiccTest&) = delete; |
| EuiccTest& operator=(const EuiccTest&) = delete; |
| |
| void SetUp() override { |
| ESimTestBase::SetUp(); |
| SetupEuicc(); |
| } |
| |
| void TearDown() override { |
| HermesProfileClient::Get()->GetTestInterface()->SetEnableProfileBehavior( |
| HermesProfileClient::TestInterface::EnableProfileBehavior:: |
| kConnectableButNotConnected); |
| } |
| |
| InstallResultPair InstallProfileFromActivationCode( |
| const mojo::Remote<mojom::Euicc>& euicc, |
| const std::string& activation_code, |
| const std::string& confirmation_code, |
| mojom::ProfileInstallMethod install_method, |
| bool wait_for_create, |
| bool wait_for_connect, |
| bool fail_connect) { |
| mojom::ProfileInstallResult out_install_result; |
| mojo::PendingRemote<mojom::ESimProfile> out_esim_profile; |
| |
| base::RunLoop run_loop; |
| euicc->InstallProfileFromActivationCode( |
| activation_code, confirmation_code, install_method, |
| base::BindLambdaForTesting( |
| [&](mojom::ProfileInstallResult install_result, |
| mojo::PendingRemote<mojom::ESimProfile> esim_profile) { |
| out_install_result = install_result; |
| out_esim_profile = std::move(esim_profile); |
| run_loop.Quit(); |
| })); |
| |
| if (wait_for_create) { |
| base::RunLoop().RunUntilIdle(); |
| // Set the enable notify profile list update back to true if it was false |
| // before and fire the pending notify profile list update and that should |
| // make the pending create callback complete. |
| cellular_esim_profile_handler()->SetEnableNotifyProfileListUpdate(true); |
| } |
| FastForwardProfileRefreshDelay(); |
| |
| if (wait_for_connect) { |
| FastForwardAutoConnectWaiting(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_LE(1u, network_connection_handler()->connect_calls().size()); |
| if (fail_connect) { |
| network_connection_handler() |
| ->connect_calls() |
| .back() |
| .InvokeErrorCallback("fake_error_name"); |
| } else { |
| network_connection_handler() |
| ->connect_calls() |
| .back() |
| .InvokeSuccessCallback(); |
| } |
| } |
| |
| run_loop.Run(); |
| return std::make_pair(out_install_result, std::move(out_esim_profile)); |
| } |
| |
| void SetErrorForNextSetPropertyAttempt(const std::string& error_name) { |
| ShillDeviceClient::Get() |
| ->GetTestInterface() |
| ->SetErrorForNextSetPropertyAttempt(error_name); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| }; |
| |
| TEST_F(EuiccTest, GetProperties) { |
| mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid); |
| ASSERT_TRUE(euicc.is_bound()); |
| mojom::EuiccPropertiesPtr properties = GetEuiccProperties(euicc); |
| EXPECT_EQ(ESimTestBase::kTestEid, properties->eid); |
| EXPECT_EQ(true, properties->is_active); |
| } |
| |
| TEST_F(EuiccTest, GetProfileList) { |
| mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid); |
| ASSERT_TRUE(euicc.is_bound()); |
| std::vector<mojo::PendingRemote<mojom::ESimProfile>> esim_profile_list = |
| GetProfileList(euicc); |
| EXPECT_EQ(0u, esim_profile_list.size()); |
| |
| HermesEuiccClient::TestInterface* euicc_test = |
| HermesEuiccClient::Get()->GetTestInterface(); |
| dbus::ObjectPath active_profile_path = euicc_test->AddFakeCarrierProfile( |
| dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kActive, "", |
| HermesEuiccClient::TestInterface::AddCarrierProfileBehavior:: |
| kAddProfileWithService); |
| dbus::ObjectPath pending_profile_path = euicc_test->AddFakeCarrierProfile( |
| dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "", |
| HermesEuiccClient::TestInterface::AddCarrierProfileBehavior:: |
| kAddProfileWithService); |
| base::RunLoop().RunUntilIdle(); |
| |
| esim_profile_list = GetProfileList(euicc); |
| EXPECT_EQ(2u, esim_profile_list.size()); |
| } |
| |
| TEST_F(EuiccTest, InstallProfileFromActivationCode) { |
| base::HistogramTester histogram_tester; |
| mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid); |
| ASSERT_TRUE(euicc.is_bound()); |
| |
| HermesEuiccClient::TestInterface* euicc_test = |
| HermesEuiccClient::Get()->GetTestInterface(); |
| // Verify that the callback is not completed if update profile list |
| // notification is not fired and save the callback until an update profile |
| // list gets called. |
| cellular_esim_profile_handler()->SetEnableNotifyProfileListUpdate(false); |
| InstallResultPair result_pair = InstallProfileFromActivationCode( |
| euicc, euicc_test->GenerateFakeActivationCode(), |
| /*confirmation_code=*/std::string(), |
| /*install_method=*/mojom::ProfileInstallMethod::kViaSmds, |
| /*wait_for_create=*/true, |
| /*wait_for_connect=*/false, /*fail_connect=*/false); |
| EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, result_pair.first); |
| EXPECT_TRUE(result_pair.second.is_valid()); |
| histogram_tester.ExpectBucketCount( |
| CellularNetworkMetricsLogger::kESimUserInstallMethod, |
| CellularNetworkMetricsLogger::ESimUserInstallMethod::kViaSmds, |
| /*expected_count=*/1); |
| |
| size_t expected_total_count = 1; |
| histogram_tester.ExpectTotalCount( |
| CellularNetworkMetricsLogger::kESimUserInstallMethod, |
| expected_total_count); |
| |
| const std::vector< |
| std::pair<mojom::ProfileInstallMethod, |
| CellularNetworkMetricsLogger::ESimUserInstallMethod>> |
| kInstallMethodPairs = { |
| { |
| mojom::ProfileInstallMethod::kViaQrCodeAfterSmds, |
| CellularNetworkMetricsLogger::ESimUserInstallMethod:: |
| kViaQrCodeAfterSmds, |
| }, |
| { |
| mojom::ProfileInstallMethod::kViaQrCodeSkippedSmds, |
| CellularNetworkMetricsLogger::ESimUserInstallMethod:: |
| kViaQrCodeSkippedSmds, |
| }, |
| { |
| mojom::ProfileInstallMethod::kViaActivationCodeAfterSmds, |
| CellularNetworkMetricsLogger::ESimUserInstallMethod:: |
| kViaActivationCodeAfterSmds, |
| }, |
| { |
| mojom::ProfileInstallMethod::kViaActivationCodeSkippedSmds, |
| CellularNetworkMetricsLogger::ESimUserInstallMethod:: |
| kViaActivationCodeSkippedSmds, |
| }, |
| }; |
| |
| for (const auto& install_method_pair : kInstallMethodPairs) { |
| // Verify that install succeeds when valid activation code is passed. |
| result_pair = InstallProfileFromActivationCode( |
| euicc, euicc_test->GenerateFakeActivationCode(), |
| /*confirmation_code=*/std::string(), |
| /*install_method=*/install_method_pair.first, /*wait_for_create=*/false, |
| /*wait_for_connect=*/false, /*fail_connect=*/false); |
| EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, result_pair.first); |
| EXPECT_TRUE(result_pair.second.is_valid()); |
| |
| histogram_tester.ExpectBucketCount( |
| CellularNetworkMetricsLogger::kESimUserInstallMethod, |
| install_method_pair.second, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount( |
| CellularNetworkMetricsLogger::kESimUserInstallMethod, |
| ++expected_total_count); |
| } |
| } |
| |
| TEST_F(EuiccTest, InstallProfileAlreadyConnected) { |
| mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid); |
| ASSERT_TRUE(euicc.is_bound()); |
| |
| HermesProfileClient::Get()->GetTestInterface()->SetEnableProfileBehavior( |
| HermesProfileClient::TestInterface::EnableProfileBehavior:: |
| kConnectableAndConnected); |
| |
| InstallResultPair result_pair = InstallProfileFromActivationCode( |
| euicc, |
| HermesEuiccClient::Get() |
| ->GetTestInterface() |
| ->GenerateFakeActivationCode(), |
| /*confirmation_code=*/std::string(), |
| /*install_method=*/mojom::ProfileInstallMethod::kViaQrCodeAfterSmds, |
| /*wait_for_create=*/false, |
| /*wait_for_connect=*/false, /*fail_connect=*/false); |
| EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, result_pair.first); |
| } |
| |
| TEST_F(EuiccTest, InstallPendingProfileFromActivationCode) { |
| mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid); |
| ASSERT_TRUE(euicc.is_bound()); |
| |
| HermesEuiccClient::TestInterface* euicc_test = |
| HermesEuiccClient::Get()->GetTestInterface(); |
| // Verify that installing a pending profile with its activation code returns |
| // proper status code and profile object. |
| dbus::ObjectPath profile_path = euicc_test->AddFakeCarrierProfile( |
| dbus::ObjectPath(kTestEuiccPath), hermes::profile::State::kPending, "", |
| HermesEuiccClient::TestInterface::AddCarrierProfileBehavior:: |
| kAddProfileWithService); |
| base::RunLoop().RunUntilIdle(); |
| HermesProfileClient::Properties* dbus_properties = |
| HermesProfileClient::Get()->GetProperties(profile_path); |
| |
| // Adding a pending profile causes a list change. |
| EXPECT_EQ(1u, observer()->profile_list_change_calls().size()); |
| |
| InstallResultPair result_pair = InstallProfileFromActivationCode( |
| euicc, dbus_properties->activation_code().value(), |
| /*confirmation_code=*/std::string(), |
| /*install_method=*/mojom::ProfileInstallMethod::kViaQrCodeAfterSmds, |
| /*wait_for_create=*/false, |
| /*wait_for_connect=*/true, /*fail_connect=*/false); |
| EXPECT_EQ(mojom::ProfileInstallResult::kSuccess, result_pair.first); |
| ASSERT_TRUE(result_pair.second.is_valid()); |
| |
| mojo::Remote<mojom::ESimProfile> esim_profile(std::move(result_pair.second)); |
| mojom::ESimProfilePropertiesPtr mojo_properties = |
| GetESimProfileProperties(esim_profile); |
| EXPECT_EQ(dbus_properties->iccid().value(), mojo_properties->iccid); |
| |
| // Installing a profile causes a list change. |
| EXPECT_EQ(3u, observer()->profile_list_change_calls().size()); |
| } |
| |
| TEST_F(EuiccTest, GetEidQRCode) { |
| mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid); |
| ASSERT_TRUE(euicc.is_bound()); |
| |
| mojom::QRCodePtr qr_code_result; |
| base::RunLoop run_loop; |
| euicc->GetEidQRCode(base::BindOnce( |
| [](mojom::QRCodePtr* out, base::OnceClosure quit_closure, |
| mojom::QRCodePtr properties) { |
| *out = std::move(properties); |
| std::move(quit_closure).Run(); |
| }, |
| &qr_code_result, run_loop.QuitClosure())); |
| run_loop.Run(); |
| |
| ASSERT_FALSE(qr_code_result.is_null()); |
| EXPECT_LT(0, qr_code_result->size); |
| } |
| |
| TEST_F(EuiccTest, RequestAvailableProfiles) { |
| mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid); |
| ASSERT_TRUE(euicc.is_bound()); |
| |
| std::optional<mojom::ESimOperationResult> result; |
| std::optional<std::vector<mojom::ESimProfilePropertiesPtr>> |
| profile_properties_list; |
| |
| base::RunLoop run_loop; |
| euicc->RequestAvailableProfiles(base::BindLambdaForTesting( |
| [&](mojom::ESimOperationResult returned_result, |
| std::vector<mojom::ESimProfilePropertiesPtr> |
| returned_profile_properties_list) { |
| result = returned_result; |
| profile_properties_list = std::move(returned_profile_properties_list); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_EQ(*result, mojom::ESimOperationResult::kSuccess); |
| |
| const std::vector<std::string> smds_activation_codes = |
| cellular_utils::GetSmdsActivationCodes(); |
| |
| ASSERT_TRUE(profile_properties_list.has_value()); |
| EXPECT_EQ(smds_activation_codes.size(), profile_properties_list->size()); |
| |
| for (const auto& profile_properties : *profile_properties_list) { |
| EXPECT_EQ(profile_properties->eid, GetEuiccProperties(euicc)->eid); |
| EXPECT_NE( |
| std::find(smds_activation_codes.begin(), smds_activation_codes.end(), |
| profile_properties->activation_code), |
| smds_activation_codes.end()); |
| } |
| } |
| |
| TEST_F(EuiccTest, RequestAvailableProfiles_FailToInhibit) { |
| mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid); |
| ASSERT_TRUE(euicc.is_bound()); |
| |
| // The cellular device is inhibited by setting a device property. Simulate a |
| // failure to inhibit by making the next attempt to set a property fail. |
| SetErrorForNextSetPropertyAttempt("error_name"); |
| |
| std::optional<mojom::ESimOperationResult> result; |
| std::optional<std::vector<mojom::ESimProfilePropertiesPtr>> |
| profile_properties_list; |
| |
| { |
| base::RunLoop run_loop; |
| euicc->RequestAvailableProfiles(base::BindLambdaForTesting( |
| [&](mojom::ESimOperationResult returned_result, |
| std::vector<mojom::ESimProfilePropertiesPtr> |
| returned_profile_properties_list) { |
| result = returned_result; |
| profile_properties_list = std::move(returned_profile_properties_list); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_EQ(*result, mojom::ESimOperationResult::kFailure); |
| |
| ASSERT_TRUE(profile_properties_list.has_value()); |
| EXPECT_TRUE(profile_properties_list->empty()); |
| |
| { |
| base::RunLoop run_loop; |
| euicc->RequestAvailableProfiles(base::BindLambdaForTesting( |
| [&](mojom::ESimOperationResult returned_result, |
| std::vector<mojom::ESimProfilePropertiesPtr> |
| returned_profile_properties_list) { |
| result = returned_result; |
| profile_properties_list = std::move(returned_profile_properties_list); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| EXPECT_EQ(*result, mojom::ESimOperationResult::kSuccess); |
| |
| const std::vector<std::string> smds_activation_codes = |
| cellular_utils::GetSmdsActivationCodes(); |
| |
| EXPECT_EQ(smds_activation_codes.size(), profile_properties_list->size()); |
| |
| for (const auto& profile_properties : *profile_properties_list) { |
| EXPECT_EQ(profile_properties->eid, GetEuiccProperties(euicc)->eid); |
| EXPECT_NE( |
| std::find(smds_activation_codes.begin(), smds_activation_codes.end(), |
| profile_properties->activation_code), |
| smds_activation_codes.end()); |
| } |
| } |
| |
| TEST_F(EuiccTest, RefreshInstalledProfiles) { |
| mojo::Remote<mojom::Euicc> euicc = GetEuiccForEid(ESimTestBase::kTestEid); |
| ASSERT_TRUE(euicc.is_bound()); |
| |
| HermesEuiccClient::TestInterface* euicc_test = |
| HermesEuiccClient::Get()->GetTestInterface(); |
| |
| // Failing to inhibit the cellular device will cause the refresh attempt to |
| // fail. |
| SetErrorForNextSetPropertyAttempt("error_name"); |
| EXPECT_EQ(mojom::ESimOperationResult::kFailure, |
| RefreshInstalledProfiles(euicc)); |
| |
| euicc_test->QueueHermesErrorStatus(HermesResponseStatus::kErrorNoResponse); |
| EXPECT_EQ(mojom::ESimOperationResult::kFailure, |
| RefreshInstalledProfiles(euicc)); |
| |
| EXPECT_EQ(mojom::ESimOperationResult::kSuccess, |
| RefreshInstalledProfiles(euicc)); |
| } |
| |
| } // namespace ash::cellular_setup |