// 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/keep_alive_scheduler.h"
#include <memory>
#include <vector>
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/task_environment.h"
#include "base/timer/mock_timer.h"
#include "chromeos/ash/components/multidevice/remote_device_test_util.h"
#include "chromeos/ash/components/tether/device_id_tether_network_guid_map.h"
#include "chromeos/ash/components/tether/fake_active_host.h"
#include "chromeos/ash/components/tether/fake_host_connection.h"
#include "chromeos/ash/components/tether/fake_host_scan_cache.h"
#include "chromeos/ash/components/tether/proto_test_util.h"
#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_connection_attempt.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kTetherNetworkGuid[] = "tetherNetworkGuid";
const char kWifiNetworkGuid[] = "wifiNetworkGuid";
const char kRemoteDevicePublicKey[] = "remoteDevicePublicKey";
const char kTestCellProvider[] = "cellProvider";
class OperationDeletedHandler {
public:
virtual void OnOperationDeleted() = 0;
};
} // namespace
namespace ash::tether {
class FakeKeepAliveOperation : public KeepAliveOperation {
public:
FakeKeepAliveOperation(
const TetherHost& tether_host,
raw_ptr<HostConnection::Factory> host_connection_factory,
OperationDeletedHandler* handler)
: KeepAliveOperation(tether_host, host_connection_factory),
tether_host_(tether_host),
handler_(handler) {}
~FakeKeepAliveOperation() override { handler_->OnOperationDeleted(); }
void SendOperationFinishedEvent(std::unique_ptr<DeviceStatus> device_status) {
device_status_ = std::move(device_status);
OnOperationFinished();
}
const TetherHost& get_tether_host() { return tether_host_; }
private:
TetherHost tether_host_;
raw_ptr<OperationDeletedHandler> handler_;
};
class FakeKeepAliveOperationFactory final : public KeepAliveOperation::Factory,
public OperationDeletedHandler {
public:
FakeKeepAliveOperationFactory()
: num_created_(0), num_deleted_(0), last_created_(nullptr) {}
~FakeKeepAliveOperationFactory() override = default;
uint32_t num_created() { return num_created_; }
uint32_t num_deleted() { return num_deleted_; }
FakeKeepAliveOperation* last_created() { return last_created_; }
void OnOperationDeleted() override {
num_deleted_++;
last_created_ = nullptr;
}
protected:
std::unique_ptr<KeepAliveOperation> CreateInstance(
const TetherHost& tether_host,
raw_ptr<HostConnection::Factory> host_connection_factory) override {
num_created_++;
last_created_ =
new FakeKeepAliveOperation(tether_host, host_connection_factory, this);
return base::WrapUnique(last_created_.get());
}
private:
uint32_t num_created_;
uint32_t num_deleted_;
raw_ptr<FakeKeepAliveOperation> last_created_;
};
class KeepAliveSchedulerTest : public testing::Test {
public:
KeepAliveSchedulerTest(const KeepAliveSchedulerTest&) = delete;
KeepAliveSchedulerTest& operator=(const KeepAliveSchedulerTest&) = delete;
protected:
KeepAliveSchedulerTest()
: test_remote_device_(multidevice::RemoteDeviceRefBuilder()
.SetPublicKey(kRemoteDevicePublicKey)
.Build()) {}
void SetUp() override {
fake_host_connection_factory_ =
std::make_unique<FakeHostConnection::Factory>();
fake_active_host_ = std::make_unique<FakeActiveHost>();
fake_host_scan_cache_ = std::make_unique<FakeHostScanCache>();
device_id_tether_network_guid_map_ =
std::make_unique<DeviceIdTetherNetworkGuidMap>();
fake_operation_factory_ =
base::WrapUnique(new FakeKeepAliveOperationFactory());
KeepAliveOperation::Factory::SetFactoryForTesting(
fake_operation_factory_.get());
scheduler_ = base::WrapUnique(new KeepAliveScheduler(
fake_host_connection_factory_.get(), fake_active_host_.get(),
fake_host_scan_cache_.get(), device_id_tether_network_guid_map_.get(),
std::make_unique<base::MockRepeatingTimer>()));
}
void VerifyTimerRunning(bool is_running) {
EXPECT_EQ(is_running, GetSchedulerTimer()->IsRunning());
if (is_running) {
EXPECT_EQ(base::Minutes(KeepAliveScheduler::kKeepAliveIntervalMinutes),
GetSchedulerTimer()->GetCurrentDelay());
}
}
void SendOperationFinishedEventFromLastCreatedOperation(
const std::string& cell_provider,
int battery_percentage,
int connection_strength) {
fake_operation_factory_->last_created()->SendOperationFinishedEvent(
std::make_unique<DeviceStatus>(CreateTestDeviceStatus(
cell_provider, battery_percentage, connection_strength)));
}
void VerifyCacheUpdated(multidevice::RemoteDeviceRef remote_device,
const std::string& carrier,
int battery_percentage,
int signal_strength) {
const HostScanCacheEntry* entry = fake_host_scan_cache_->GetCacheEntry(
device_id_tether_network_guid_map_->GetTetherNetworkGuidForDeviceId(
remote_device.GetDeviceId()));
ASSERT_TRUE(entry);
EXPECT_EQ(carrier, entry->carrier);
EXPECT_EQ(battery_percentage, entry->battery_percentage);
EXPECT_EQ(signal_strength, entry->signal_strength);
}
base::MockRepeatingTimer* GetSchedulerTimer() {
return static_cast<base::MockRepeatingTimer*>(scheduler_->timer_.get());
}
base::test::TaskEnvironment task_environment_;
multidevice::RemoteDeviceRef test_remote_device_;
std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
std::unique_ptr<FakeActiveHost> fake_active_host_;
std::unique_ptr<FakeHostScanCache> fake_host_scan_cache_;
// TODO(hansberry): Use a fake for this when a real mapping scheme is created.
std::unique_ptr<DeviceIdTetherNetworkGuidMap>
device_id_tether_network_guid_map_;
// raw_ptr<base::MockRepeatingTimer> GetSchedulerTimer();
std::unique_ptr<FakeKeepAliveOperationFactory> fake_operation_factory_;
std::unique_ptr<KeepAliveScheduler> scheduler_;
};
TEST_F(KeepAliveSchedulerTest, TestSendTickle_OneActiveHost) {
EXPECT_FALSE(fake_operation_factory_->num_created());
EXPECT_FALSE(fake_operation_factory_->num_deleted());
VerifyTimerRunning(/*is_running=*/false);
// Start connecting to a device. No operation should be started.
fake_active_host_->SetActiveHostConnecting(test_remote_device_.GetDeviceId(),
std::string(kTetherNetworkGuid));
EXPECT_FALSE(fake_operation_factory_->num_created());
EXPECT_FALSE(fake_operation_factory_->num_deleted());
VerifyTimerRunning(/*is_running=*/false);
// Connect to the device; the operation should be started.
fake_active_host_->SetActiveHostConnected(test_remote_device_.GetDeviceId(),
std::string(kTetherNetworkGuid),
std::string(kWifiNetworkGuid));
EXPECT_EQ(1u, fake_operation_factory_->num_created());
EXPECT_EQ(test_remote_device_, fake_operation_factory_->last_created()
->get_tether_host()
.remote_device_ref());
EXPECT_FALSE(fake_operation_factory_->num_deleted());
VerifyTimerRunning(/*is_running=*/true);
// Ensure that once the operation is finished, it is deleted.
SendOperationFinishedEventFromLastCreatedOperation(
kTestCellProvider, /*battery_percentage=*/50, /*connection_strength=*/2);
EXPECT_EQ(1u, fake_operation_factory_->num_created());
EXPECT_EQ(1u, fake_operation_factory_->num_deleted());
VerifyTimerRunning(/*is_running=*/true);
VerifyCacheUpdated(test_remote_device_, kTestCellProvider,
/*battery_percentage=*/50, /*signal_strength=*/50);
// Fire the timer; this should result in tickle #2 being sent.
GetSchedulerTimer()->Fire();
EXPECT_EQ(2u, fake_operation_factory_->num_created());
EXPECT_EQ(test_remote_device_, fake_operation_factory_->last_created()
->get_tether_host()
.remote_device_ref());
EXPECT_EQ(1u, fake_operation_factory_->num_deleted());
VerifyTimerRunning(/*is_running=*/true);
// Finish tickle operation #2.
SendOperationFinishedEventFromLastCreatedOperation(
kTestCellProvider, /*battery_percentage=*/40, /*connection_strength=*/3);
EXPECT_EQ(2u, fake_operation_factory_->num_created());
EXPECT_EQ(2u, fake_operation_factory_->num_deleted());
VerifyTimerRunning(/*is_running=*/true);
VerifyCacheUpdated(test_remote_device_, kTestCellProvider,
/*battery_percentage=*/40, /*signal_strength=*/75);
// Fire the timer; this should result in tickle #3 being sent.
GetSchedulerTimer()->Fire();
EXPECT_EQ(3u, fake_operation_factory_->num_created());
EXPECT_EQ(test_remote_device_, fake_operation_factory_->last_created()
->get_tether_host()
.remote_device_ref());
EXPECT_EQ(2u, fake_operation_factory_->num_deleted());
VerifyTimerRunning(/*is_running=*/true);
// Finish tickler operation #3. This time, simulate a failure to receive a
// DeviceStatus back.
fake_operation_factory_->last_created()->SendOperationFinishedEvent(nullptr);
EXPECT_EQ(3u, fake_operation_factory_->num_created());
EXPECT_EQ(3u, fake_operation_factory_->num_deleted());
VerifyTimerRunning(/*is_running=*/true);
// The same data returned by tickle #2 should be present.
VerifyCacheUpdated(test_remote_device_, kTestCellProvider,
/*battery_percentage=*/40, /*signal_strength=*/75);
// Disconnect that device.
fake_active_host_->SetActiveHostDisconnected();
EXPECT_EQ(3u, fake_operation_factory_->num_created());
EXPECT_EQ(3u, fake_operation_factory_->num_deleted());
VerifyTimerRunning(/*is_running=*/false);
}
} // namespace ash::tether