blob: d867fde53ecad9962f6fa9adf45c391f6a70e3ba [file] [log] [blame]
// Copyright (c) 2015 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 "chrome/browser/chromeos/policy/heartbeat_scheduler.h"
#include <stdint.h>
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/test_simple_task_runner.h"
#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
#include "chromeos/settings/cros_settings_names.h"
#include "components/gcm_driver/common/gcm_messages.h"
#include "components/gcm_driver/fake_gcm_driver.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "net/base/ip_endpoint.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::Contains;
using ::testing::Field;
using ::testing::Key;
using ::testing::Matches;
using ::testing::Pair;
using ::testing::SaveArg;
namespace {
const char* const kFakeEnrollmentDomain = "example.com";
const char* const kFakeDeviceId = "fake_device_id";
const char* const kHeartbeatGCMAppID = "com.google.chromeos.monitoring";
const char* const kRegistrationId = "registration_id";
const char* const kDMToken = "fake_dm_token";
MATCHER(IsHeartbeatMsg, "") {
return Matches(
Field(&gcm::OutgoingMessage::data, Contains(Pair("type", "hb"))))(arg);
}
MATCHER(IsUpstreamNotificationMsg, "") {
return Matches(Field(&gcm::OutgoingMessage::data, Contains(Key("notify"))))(
arg);
}
class MockGCMDriver : public testing::StrictMock<gcm::FakeGCMDriver> {
public:
MockGCMDriver() { IgnoreDefaultHeartbeatsInterval(); }
~MockGCMDriver() override {
}
MOCK_METHOD2(RegisterImpl,
void(const std::string&, const std::vector<std::string>&));
MOCK_METHOD3(SendImpl,
void(const std::string&,
const std::string&,
const gcm::OutgoingMessage& message));
MOCK_METHOD2(AddHeartbeatInterval,
void(const std::string& scope, int interval_ms));
// Helper function to complete a registration previously started by
// Register().
void CompleteRegistration(const std::string& app_id,
gcm::GCMClient::Result result) {
RegisterFinished(app_id, kRegistrationId, result);
}
// Helper function to complete a send operation previously started by
// Send().
void CompleteSend(const std::string& app_id,
const std::string& message_id,
gcm::GCMClient::Result result) {
SendFinished(app_id, message_id, result);
}
void AddConnectionObserver(gcm::GCMConnectionObserver* observer) override {
EXPECT_FALSE(observer_);
observer_ = observer;
}
void RemoveConnectionObserver(gcm::GCMConnectionObserver* observer) override {
EXPECT_TRUE(observer_);
observer_ = nullptr;
}
void NotifyConnected() {
ASSERT_TRUE(observer_);
observer_->OnConnected(net::IPEndPoint());
}
void IgnoreDefaultHeartbeatsInterval() {
// Ignore default setting for heartbeats interval.
EXPECT_CALL(*this, AddHeartbeatInterval(_, 2 * 60 * 1000))
.Times(AnyNumber());
}
void ResetStore() {
// Defensive copy in case OnStoreReset calls Add/RemoveAppHandler.
std::vector<gcm::GCMAppHandler*> app_handler_values;
for (const auto& key_value : app_handlers())
app_handler_values.push_back(key_value.second);
for (gcm::GCMAppHandler* app_handler : app_handler_values) {
app_handler->OnStoreReset();
// app_handler might now have been deleted.
}
}
private:
gcm::GCMConnectionObserver* observer_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(MockGCMDriver);
};
class HeartbeatSchedulerTest : public testing::Test {
public:
HeartbeatSchedulerTest()
: task_runner_(new base::TestSimpleTaskRunner()),
scheduler_(&gcm_driver_,
&cloud_policy_client_,
kFakeEnrollmentDomain,
kFakeDeviceId,
task_runner_) {}
void SetUp() override {
settings_helper_.ReplaceProvider(chromeos::kHeartbeatEnabled);
}
void TearDown() override { content::RunAllTasksUntilIdle(); }
void CheckPendingTaskDelay(base::Time last_heartbeat,
base::TimeDelta expected_delay) {
EXPECT_FALSE(last_heartbeat.is_null());
base::Time now = base::Time::NowFromSystemTime();
EXPECT_GE(now, last_heartbeat);
base::TimeDelta actual_delay = task_runner_->NextPendingTaskDelay();
// NextPendingTaskDelay() returns the exact original delay value the task
// was posted with. The heartbeat task would have been calculated to fire at
// |last_heartbeat| + |expected_delay|, but we don't know the exact time
// when the task was posted (if it was a couple of milliseconds after
// |last_heartbeat|, then |actual_delay| would be a couple of milliseconds
// smaller than |expected_delay|.
//
// We do know that the task was posted sometime between |last_heartbeat|
// and |now|, so we know that 0 <= |expected_delay| - |actual_delay| <=
// |now| - |last_heartbeat|.
base::TimeDelta delta = expected_delay - actual_delay;
EXPECT_LE(base::TimeDelta(), delta);
EXPECT_GE(now - last_heartbeat, delta);
}
void IgnoreUpstreamNotificationMsg() {
EXPECT_CALL(gcm_driver_,
SendImpl(kHeartbeatGCMAppID, _, IsUpstreamNotificationMsg()))
.Times(AnyNumber());
}
content::TestBrowserThreadBundle test_browser_thread_bundle_;
MockGCMDriver gcm_driver_;
chromeos::ScopedCrosSettingsTestHelper settings_helper_;
testing::NiceMock<policy::MockCloudPolicyClient> cloud_policy_client_;
// TaskRunner used to run individual tests.
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
// The HeartbeatScheduler instance under test.
policy::HeartbeatScheduler scheduler_;
};
TEST_F(HeartbeatSchedulerTest, Basic) {
// Just makes sure we can spin up and shutdown the scheduler with
// heartbeats disabled.
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, false);
ASSERT_FALSE(task_runner_->HasPendingTask());
}
TEST_F(HeartbeatSchedulerTest, PermanentlyFailedGCMRegistration) {
// If heartbeats are enabled, we should register with GCMDriver.
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, true);
gcm_driver_.CompleteRegistration(
kHeartbeatGCMAppID, gcm::GCMClient::GCM_DISABLED);
// There should be no heartbeat tasks pending, because registration failed.
ASSERT_FALSE(task_runner_->HasPendingTask());
}
TEST_F(HeartbeatSchedulerTest, TemporarilyFailedGCMRegistration) {
IgnoreUpstreamNotificationMsg();
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, true);
gcm_driver_.CompleteRegistration(
kHeartbeatGCMAppID, gcm::GCMClient::SERVER_ERROR);
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
IgnoreUpstreamNotificationMsg();
gcm_driver_.IgnoreDefaultHeartbeatsInterval();
// Should have a pending task to try registering again.
ASSERT_TRUE(task_runner_->HasPendingTask());
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
task_runner_->RunPendingTasks();
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
IgnoreUpstreamNotificationMsg();
gcm_driver_.IgnoreDefaultHeartbeatsInterval();
// Once we have successfully registered, we should send a heartbeat.
EXPECT_CALL(gcm_driver_, SendImpl(kHeartbeatGCMAppID, _, IsHeartbeatMsg()));
gcm_driver_.CompleteRegistration(
kHeartbeatGCMAppID, gcm::GCMClient::SUCCESS);
task_runner_->RunPendingTasks();
}
TEST_F(HeartbeatSchedulerTest, StoreResetDuringRegistration) {
IgnoreUpstreamNotificationMsg();
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, true);
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
gcm_driver_.ResetStore();
IgnoreUpstreamNotificationMsg();
// Successful registration handled ok despite store reset.
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_CALL(gcm_driver_, SendImpl(kHeartbeatGCMAppID, _, IsHeartbeatMsg()));
gcm_driver_.CompleteRegistration(
kHeartbeatGCMAppID, gcm::GCMClient::SUCCESS);
EXPECT_EQ(1U, task_runner_->NumPendingTasks());
task_runner_->RunPendingTasks();
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
}
TEST_F(HeartbeatSchedulerTest, StoreResetAfterRegistration) {
IgnoreUpstreamNotificationMsg();
// Start from a successful registration.
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, true);
EXPECT_CALL(gcm_driver_, SendImpl(kHeartbeatGCMAppID, _, IsHeartbeatMsg()));
gcm_driver_.CompleteRegistration(
kHeartbeatGCMAppID, gcm::GCMClient::SUCCESS);
EXPECT_EQ(1U, task_runner_->NumPendingTasks());
task_runner_->RunPendingTasks();
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
gcm_driver_.IgnoreDefaultHeartbeatsInterval();
// Reseting the store should trigger re-registration.
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
gcm_driver_.ResetStore();
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
IgnoreUpstreamNotificationMsg();
// Once we have successfully re-registered, we should send a heartbeat.
EXPECT_FALSE(task_runner_->HasPendingTask());
EXPECT_CALL(gcm_driver_, SendImpl(kHeartbeatGCMAppID, _, IsHeartbeatMsg()));
gcm_driver_.CompleteRegistration(
kHeartbeatGCMAppID, gcm::GCMClient::SUCCESS);
EXPECT_EQ(1U, task_runner_->NumPendingTasks());
task_runner_->RunPendingTasks();
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
}
TEST_F(HeartbeatSchedulerTest, ChangeHeartbeatFrequency) {
IgnoreUpstreamNotificationMsg();
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, true);
gcm_driver_.CompleteRegistration(
kHeartbeatGCMAppID, gcm::GCMClient::SUCCESS);
EXPECT_EQ(1U, task_runner_->NumPendingTasks());
// Should have a heartbeat task posted with zero delay on startup.
EXPECT_EQ(base::TimeDelta(), task_runner_->NextPendingTaskDelay());
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
IgnoreUpstreamNotificationMsg();
gcm_driver_.IgnoreDefaultHeartbeatsInterval();
const int new_delay = 1234*1000; // 1234 seconds.
EXPECT_CALL(gcm_driver_, AddHeartbeatInterval(_, new_delay));
settings_helper_.SetInteger(chromeos::kHeartbeatFrequency, new_delay);
// Now run pending heartbeat task, should send a heartbeat.
gcm::OutgoingMessage message;
EXPECT_CALL(gcm_driver_, SendImpl(kHeartbeatGCMAppID, _, IsHeartbeatMsg()))
.WillOnce(SaveArg<2>(&message));
task_runner_->RunPendingTasks();
EXPECT_FALSE(task_runner_->HasPendingTask());
// Complete sending a message - we should queue up the next heartbeat
// even if the previous attempt failed.
gcm_driver_.CompleteSend(
kHeartbeatGCMAppID, message.id, gcm::GCMClient::SERVER_ERROR);
EXPECT_EQ(1U, task_runner_->NumPendingTasks());
CheckPendingTaskDelay(scheduler_.last_heartbeat(),
base::TimeDelta::FromMilliseconds(new_delay));
}
TEST_F(HeartbeatSchedulerTest, DisableHeartbeats) {
IgnoreUpstreamNotificationMsg();
// Makes sure that we can disable heartbeats on the fly.
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, true);
gcm::OutgoingMessage message;
EXPECT_CALL(gcm_driver_, SendImpl(kHeartbeatGCMAppID, _, IsHeartbeatMsg()))
.WillOnce(SaveArg<2>(&message));
gcm_driver_.CompleteRegistration(
kHeartbeatGCMAppID, gcm::GCMClient::SUCCESS);
// Should have a heartbeat task posted.
EXPECT_EQ(1U, task_runner_->NumPendingTasks());
task_runner_->RunPendingTasks();
// Complete sending a message - we should queue up the next heartbeat.
gcm_driver_.CompleteSend(
kHeartbeatGCMAppID, message.id, gcm::GCMClient::SUCCESS);
// Should have a new heartbeat task posted.
ASSERT_EQ(1U, task_runner_->NumPendingTasks());
CheckPendingTaskDelay(
scheduler_.last_heartbeat(),
base::TimeDelta::FromMilliseconds(
policy::HeartbeatScheduler::kDefaultHeartbeatIntervalMs));
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
IgnoreUpstreamNotificationMsg();
gcm_driver_.IgnoreDefaultHeartbeatsInterval();
// Now disable heartbeats. Should get no more heartbeats sent.
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, false);
task_runner_->RunPendingTasks();
EXPECT_FALSE(task_runner_->HasPendingTask());
}
TEST_F(HeartbeatSchedulerTest, CheckMessageContents) {
IgnoreUpstreamNotificationMsg();
gcm::OutgoingMessage message;
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
EXPECT_CALL(gcm_driver_, SendImpl(kHeartbeatGCMAppID, _, IsHeartbeatMsg()))
.WillOnce(SaveArg<2>(&message));
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, true);
gcm_driver_.CompleteRegistration(
kHeartbeatGCMAppID, gcm::GCMClient::SUCCESS);
task_runner_->RunPendingTasks();
// Heartbeats should have a time-to-live equivalent to the heartbeat frequency
// so we don't have more than one heartbeat queued at a time.
EXPECT_EQ(policy::HeartbeatScheduler::kDefaultHeartbeatIntervalMs/1000,
message.time_to_live);
// Check the values in the message payload.
EXPECT_EQ("hb", message.data["type"]);
int64_t timestamp;
EXPECT_TRUE(base::StringToInt64(message.data["timestamp"], &timestamp));
EXPECT_EQ(kFakeEnrollmentDomain, message.data["domain_name"]);
EXPECT_EQ(kFakeDeviceId, message.data["device_id"]);
}
TEST_F(HeartbeatSchedulerTest, SendGcmIdUpdate) {
// Verifies that GCM id update request was sent after GCM registration.
cloud_policy_client_.SetDMToken(kDMToken);
policy::CloudPolicyClient::StatusCallback callback;
EXPECT_CALL(cloud_policy_client_, UpdateGcmId(kRegistrationId, _))
.WillOnce(SaveArg<1>(&callback));
// Enable heartbeats.
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _));
EXPECT_CALL(gcm_driver_, SendImpl(kHeartbeatGCMAppID, _, _))
.Times(AtLeast(1));
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, true);
gcm_driver_.CompleteRegistration(kHeartbeatGCMAppID, gcm::GCMClient::SUCCESS);
task_runner_->RunPendingTasks();
// Verifies that CloudPolicyClient got the update request, with a valid
// callback.
testing::Mock::VerifyAndClearExpectations(&cloud_policy_client_);
EXPECT_FALSE(callback.is_null());
callback.Run(true);
}
TEST_F(HeartbeatSchedulerTest, GcmUpstreamNotificationSignup) {
// Verifies that upstream notification works as expected.
cloud_policy_client_.SetDMToken(kDMToken);
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _))
.Times(AnyNumber());
EXPECT_CALL(cloud_policy_client_, UpdateGcmId(kRegistrationId, _));
// GCM connected event before the registration should be ignored.
settings_helper_.SetBoolean(chromeos::kHeartbeatEnabled, true);
gcm_driver_.NotifyConnected();
task_runner_->RunPendingTasks();
testing::Mock::VerifyAndClearExpectations(&gcm_driver_);
// Ignore unintested calls.
EXPECT_CALL(gcm_driver_, RegisterImpl(kHeartbeatGCMAppID, _))
.Times(AnyNumber());
EXPECT_CALL(gcm_driver_, SendImpl(kHeartbeatGCMAppID, _, IsHeartbeatMsg()))
.Times(AnyNumber());
gcm_driver_.IgnoreDefaultHeartbeatsInterval();
EXPECT_CALL(gcm_driver_,
SendImpl(kHeartbeatGCMAppID, _, IsUpstreamNotificationMsg()))
.Times(AtLeast(1));
gcm_driver_.CompleteRegistration(kHeartbeatGCMAppID, gcm::GCMClient::SUCCESS);
gcm_driver_.NotifyConnected();
}
} // namespace