blob: 5690c2aff3b508c8054638737ecc6fc0b4f4399e [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/services/multidevice_setup/grandfathered_easy_unlock_host_disabler.h"
#include <memory>
#include "base/macros.h"
#include "base/timer/mock_timer.h"
#include "chromeos/components/multidevice/remote_device_test_util.h"
#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/services/multidevice_setup/fake_host_backend_delegate.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace multidevice_setup {
namespace {
const char kEasyUnlockHostIdToDisablePrefName[] =
"multidevice_setup.easy_unlock_host_id_to_disable";
const char kNoDevice[] = "";
const size_t kNumTestDevices = 2;
} // namespace
class MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest
: public testing::Test {
protected:
MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest()
: test_devices_(
multidevice::CreateRemoteDeviceRefListForTest(kNumTestDevices)) {}
~MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest() override = default;
// testing::Test:
void SetUp() override {
fake_host_backend_delegate_ = std::make_unique<FakeHostBackendDelegate>();
fake_device_sync_client_ =
std::make_unique<device_sync::FakeDeviceSyncClient>();
fake_device_sync_client_->set_synced_devices(test_devices_);
test_pref_service_ =
std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
GrandfatheredEasyUnlockHostDisabler::RegisterPrefs(
test_pref_service_->registry());
}
void SetHost(const base::Optional<multidevice::RemoteDeviceRef>& host_device,
multidevice::SoftwareFeature host_type) {
if (host_type != multidevice::SoftwareFeature::kBetterTogetherHost &&
host_type != multidevice::SoftwareFeature::kSmartLockHost)
return;
for (const auto& remote_device : test_devices_) {
bool should_be_host =
host_device != base::nullopt &&
host_device->GetDeviceId() == remote_device.GetDeviceId();
GetMutableRemoteDevice(remote_device)->software_features[host_type] =
should_be_host ? multidevice::SoftwareFeatureState::kEnabled
: multidevice::SoftwareFeatureState::kSupported;
}
if (host_type == multidevice::SoftwareFeature::kBetterTogetherHost)
fake_host_backend_delegate_->NotifyHostChangedOnBackend(host_device);
}
void InitializeTest(
const std::string& initial_device_id_pref_value,
base::Optional<multidevice::RemoteDeviceRef> initial_better_together_host,
base::Optional<multidevice::RemoteDeviceRef> initial_easy_unlock_host) {
test_pref_service_->SetString(kEasyUnlockHostIdToDisablePrefName,
initial_device_id_pref_value);
SetHost(initial_better_together_host,
multidevice::SoftwareFeature::kBetterTogetherHost);
SetHost(initial_easy_unlock_host,
multidevice::SoftwareFeature::kSmartLockHost);
auto mock_timer = std::make_unique<base::MockOneShotTimer>();
mock_timer_ = mock_timer.get();
grandfathered_easy_unlock_host_disabler_ =
GrandfatheredEasyUnlockHostDisabler::Factory::Get()->BuildInstance(
fake_host_backend_delegate_.get(), fake_device_sync_client_.get(),
test_pref_service_.get(), std::move(mock_timer));
}
std::string GetEasyUnlockHostIdToDisablePrefValue() {
return test_pref_service_->GetString(kEasyUnlockHostIdToDisablePrefName);
}
const multidevice::RemoteDeviceRefList& test_devices() const {
return test_devices_;
}
device_sync::FakeDeviceSyncClient* fake_device_sync_client() const {
return fake_device_sync_client_.get();
}
base::MockOneShotTimer* mock_timer() const { return mock_timer_; }
private:
multidevice::RemoteDeviceRefList test_devices_;
std::unique_ptr<FakeHostBackendDelegate> fake_host_backend_delegate_;
std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
test_pref_service_;
base::MockOneShotTimer* mock_timer_ = nullptr;
std::unique_ptr<GrandfatheredEasyUnlockHostDisabler>
grandfathered_easy_unlock_host_disabler_;
DISALLOW_COPY_AND_ASSIGN(
MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest);
};
// Situation #1:
// BTH = BETTER_TOGETHER_HOST, 0 = disabled, A = devices[0]
// EUH = EASY_UNLOCK_HOST, 1 = enabled, B = devices[1]
//
// | A | B | | A | B |
// ---+---+---+ ---+---+---+
// BTH| 1 | 0 | BTH| 0 | 0 |
// ---+---+---+ ---> ---+---+---+
// EUH| 1 | 0 | EUH| 1 | 0 |
//
// Grandfathering prevents EUH from being disabled automatically. This class
// disables EUH manually.
TEST_F(
MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfBetterTogetherHostChangedFromOneDeviceToNoDeviceThenDisableEasyUnlock) {
InitializeTest(kNoDevice /* initial_device_id_pref_value */,
test_devices()[0] /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
SetHost(base::nullopt, multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(test_devices()[0].GetDeviceId(),
GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_EQ(
1,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
fake_device_sync_client()->InvokePendingSetSoftwareFeatureStateCallback(
device_sync::mojom::NetworkRequestResult::kSuccess);
EXPECT_EQ(kNoDevice, GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_FALSE(mock_timer()->IsRunning());
}
// Situation #2:
// BTH = BETTER_TOGETHER_HOST, 0 = disabled, A = devices[0]
// EUH = EASY_UNLOCK_HOST, 1 = enabled, B = devices[1]
//
// | A | B | | A | B |
// ---+---+---+ ---+---+---+
// BTH| 0 | 0 | BTH| 0 | 1 |
// ---+---+---+ ---> ---+---+---+
// EUH| 1 | 0 | EUH| 0 | 1 |
//
// The CryptAuth backend (via GmsCore) disables EUH on device A when BTH is
// enabled (exclusively) on another device, B. No action necessary from this
// class.
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfBetterTogetherHostChangedFromNoDeviceToADeviceThenDoNothing) {
InitializeTest(kNoDevice /* initial_device_id_pref_value */,
base::nullopt /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
SetHost(test_devices()[1], multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(kNoDevice, GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_EQ(
0,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
}
// Situation #3:
// BTH = BETTER_TOGETHER_HOST, 0 = disabled, A = devices[0]
// EUH = EASY_UNLOCK_HOST, 1 = enabled, B = devices[1]
//
// | A | B | | A | B |
// ---+---+---+ ---+---+---+
// BTH| 1 | 0 | BTH| 0 | 1 |
// ---+---+---+ ---> ---+---+---+
// EUH| 1 | 0 | EUH| 0 | 1 |
//
// The CryptAuth backend (via GmsCore) disables EUH on device A when BTH is
// enabled (exclusively) on another device, B. We still attempt to disable
// EUH in this case to be safe.
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfBetterTogetherHostChangedFromOneDeviceToAnotherThenDisableEasyUnlock) {
InitializeTest(kNoDevice /* initial_device_id_pref_value */,
test_devices()[0] /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
SetHost(test_devices()[1], multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(test_devices()[0].GetDeviceId(),
GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_EQ(
1,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
fake_device_sync_client()->InvokePendingSetSoftwareFeatureStateCallback(
device_sync::mojom::NetworkRequestResult::kSuccess);
EXPECT_EQ(kNoDevice, GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_FALSE(mock_timer()->IsRunning());
}
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfDisablePendingThenConstructorAttemptsToDisableEasyUnlock) {
InitializeTest(
test_devices()[0].GetDeviceId() /* initial_device_id_pref_value */,
base::nullopt /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
EXPECT_EQ(test_devices()[0].GetDeviceId(),
GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_EQ(
1,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
}
// Situation #1 where device A is removed from list of synced devices:
//
// | A | B | | A | B |
// ---+---+---+ ---+---+---+
// BTH| 1 | 0 | BTH| 0 | 0 |
// ---+---+---+ ---> ---+---+---+
// EUH| 1 | 0 | EUH| 1 | 0 |
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfHostToDisableIsNotInListOfSyncedDevicesThenClearPref) {
InitializeTest(kNoDevice /* initial_device_id_pref_value */,
test_devices()[0] /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
// Remove device[0] from list
fake_device_sync_client()->set_synced_devices({test_devices()[1]});
SetHost(base::nullopt, multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(kNoDevice, GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_EQ(
0,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
}
// Situation #1 with failure:
//
// | A | B | | A | B |
// ---+---+---+ ---+---+---+
// BTH| 1 | 0 | BTH| 0 | 0 |
// ---+---+---+ ---> ---+---+---+
// EUH| 1 | 0 | EUH| 1 | 0 |
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfEasyUnlockDisableUnsuccessfulThenScheduleRetry) {
InitializeTest(kNoDevice /* initial_device_id_pref_value */,
test_devices()[0] /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
SetHost(base::nullopt, multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(
1,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
fake_device_sync_client()->InvokePendingSetSoftwareFeatureStateCallback(
device_sync::mojom::NetworkRequestResult::kInternalServerError);
EXPECT_EQ(
0,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
EXPECT_EQ(test_devices()[0].GetDeviceId(),
GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_TRUE(mock_timer()->IsRunning());
mock_timer()->Fire();
EXPECT_EQ(
1,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
}
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfNoDisablePendingThenConstructorDoesNothing) {
InitializeTest(kNoDevice /* initial_device_id_pref_value */,
base::nullopt /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
EXPECT_EQ(kNoDevice, GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_EQ(
0,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
}
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfDisablePendingButIsNotCurrentEasyUnlockHostThenClearPref) {
InitializeTest(
test_devices()[0].GetDeviceId() /* initial_device_id_pref_value */,
test_devices()[1] /* initial_better_together_host */,
test_devices()[1] /* initial_easy_unlock_host */);
EXPECT_EQ(kNoDevice, GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_EQ(
0,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
}
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfDisablePendingButIsCurrentBetterTogetherHostThenClearPref) {
InitializeTest(
test_devices()[0].GetDeviceId() /* initial_device_id_pref_value */,
test_devices()[0] /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
EXPECT_EQ(kNoDevice, GetEasyUnlockHostIdToDisablePrefValue());
EXPECT_EQ(
0,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
}
// Simulate:
// - Disable BETTER_TOGETHER_HOST on device 0
// - GrandfatheredEasyUnlockHostDisabler tries to disable EASY_UNLOCK_HOST on
// device 0 but fails
// - Timer is running while we wait to retry
// - Re-enable BETTER_TOGETHER_HOST on device 0
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfHostChangesWhileRetryTimerIsRunningThenCancelTimerAndClearPref) {
InitializeTest(kNoDevice /* initial_device_id_pref_value */,
test_devices()[0] /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
SetHost(base::nullopt, multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(
1,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
fake_device_sync_client()->InvokePendingSetSoftwareFeatureStateCallback(
device_sync::mojom::NetworkRequestResult::kInternalServerError);
EXPECT_TRUE(mock_timer()->IsRunning());
SetHost(test_devices()[0], multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(
0,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
EXPECT_FALSE(mock_timer()->IsRunning());
EXPECT_EQ(kNoDevice, GetEasyUnlockHostIdToDisablePrefValue());
}
// Simulate:
// - Set device 0 as host
// - Disable host
// - Set device 1 as host
// - Disable host
// - SetSoftwareFeatureState callback for device 0 is called
TEST_F(MultiDeviceSetupGrandfatheredEasyUnlockHostDisablerTest,
IfDifferentHostDisabledBeforeFirstCallbackThenFirstCallbackDoesNothing) {
InitializeTest(kNoDevice /* initial_device_id_pref_value */,
test_devices()[0] /* initial_better_together_host */,
test_devices()[0] /* initial_easy_unlock_host */);
SetHost(base::nullopt, multidevice::SoftwareFeature::kBetterTogetherHost);
SetHost(test_devices()[1], multidevice::SoftwareFeature::kBetterTogetherHost);
SetHost(test_devices()[1], multidevice::SoftwareFeature::kSmartLockHost);
SetHost(base::nullopt, multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(
2,
fake_device_sync_client()->GetSetSoftwareFeatureStateCallbackQueueSize());
EXPECT_EQ(test_devices()[1].GetDeviceId(),
GetEasyUnlockHostIdToDisablePrefValue());
fake_device_sync_client()->InvokePendingSetSoftwareFeatureStateCallback(
device_sync::mojom::NetworkRequestResult::kSuccess);
EXPECT_EQ(test_devices()[1].GetDeviceId(),
GetEasyUnlockHostIdToDisablePrefValue());
}
} // namespace multidevice_setup
} // namespace chromeos