blob: 8ea7f9c019ca5896013628e06d310af2d5ed0c5d [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/cloud/cloud_policy_invalidator.h"
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/sample_map.h"
#include "base/metrics/statistics_recorder.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/invalidation/test_support/fake_invalidation_listener.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/cloud_policy_core.h"
#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
#include "components/policy/core/common/cloud/enterprise_metrics.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
#include "components/policy/core/common/cloud/policy_invalidation_scope.h"
#include "components/policy/core/common/cloud/policy_invalidation_util.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::WithArg;
namespace em = enterprise_management;
namespace policy {
namespace {
// Fake policy values which are alternated to cause the store to report a
// changed policy.
constexpr char kPolicyValueA[] = "policyValueA";
constexpr char kPolicyValueB[] = "policyValueB";
constexpr char kDeviceLocalAccountId[] = "test_account";
} // namespace
class CloudPolicyInvalidatorTestBase : public testing::Test {
protected:
CloudPolicyInvalidatorTestBase();
~CloudPolicyInvalidatorTestBase() override;
// Starts the invalidator which will be tested.
void StartInvalidator();
const CloudPolicyInvalidator* invalidator() const {
return invalidator_.get();
}
// Connects the cloud policy core.
void ConnectCore();
// Starts the refresh scheduler.
void StartRefreshScheduler();
// Disconnects the cloud policy core.
void DisconnectCore();
// Simulates storing a new policy to the policy store.
// `policy_changed` determines whether a policy value different from the
// current value will be stored.
// `time` determines the timestamp the store will report.
void StorePolicy(int64_t invalidation_version,
bool policy_changed,
const base::Time& time);
void StorePolicy(int64_t invalidation_version, bool policy_changed) {
StorePolicy(invalidation_version, policy_changed, Now() - base::Minutes(5));
}
void StorePolicy(int64_t invalidation_version) {
StorePolicy(invalidation_version, false);
}
void StorePolicy() { StorePolicy(0); }
// Disables the invalidation service. It is disabled by default.
void DisableInvalidationListener();
// Enables the invalidation service. It is disabled by default.
void EnableInvalidationListener();
// Causes the invalidation service to fire an invalidation.
invalidation::DirectInvalidation FireInvalidation(int64_t version,
const std::string& payload);
// Returns true if the invalidation info of the `core_`'s client matches the
// passed invalidation's version and payload.
bool ClientInvalidationInfoMatches(
const invalidation::DirectInvalidation& invalidation);
// Returns true if the invalidation info of the `core_`'s client is unset.
bool ClientInvalidationInfoIsUnset();
// Returns the number of policy refreshes that have occurred since the last
// call to this method and resets the counter to 0.
int GetPolicyRefreshCountAndReset();
// Returns the invalidations enabled state set by the invalidator on the
// refresh scheduler.
bool InvalidationsEnabled();
// Determines if the invalidator has registered as an observer with the
// invalidation service.
bool IsInvalidatorRegistered();
// Advance the test clock by the given `delta`.
void FastForwardBy(base::TimeDelta delta);
// Advance the test clock to trigger invalidation handling.
void FastForwardByInvalidationDelay();
// Get the current time on the test clock.
base::Time Now();
// Translate a version number into an appropriate invalidation version (which
// is based on the start time).
int64_t V(int version);
// Get an invalidation version for the given time.
int64_t GetVersion(base::Time time);
// Get the invalidation scope that the `invalidator_` is responsible for.
virtual PolicyInvalidationScope GetPolicyInvalidationScope() const;
std::string GetPolicyInvalidationType() const;
private:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
const base::Time start_time{task_environment_.GetMockClock()->Now()};
// Objects the invalidator depends on.
testing::NiceMock<MockCloudPolicyStore> store_;
CloudPolicyCore core_{dm_protocol::GetChromeUserPolicyType(), std::string(),
&store_, task_environment_.GetMainThreadTaskRunner(),
network::TestNetworkConnectionTracker::CreateGetter()};
int policy_refresh_count_ = 0;
invalidation::FakeInvalidationListener invalidation_listener_;
// The currently used policy value.
std::string policy_value_cur_{kPolicyValueA};
// The invalidator which will be tested.
std::unique_ptr<CloudPolicyInvalidator> invalidator_;
};
CloudPolicyInvalidatorTestBase::CloudPolicyInvalidatorTestBase() = default;
CloudPolicyInvalidatorTestBase::~CloudPolicyInvalidatorTestBase() {
core_.Disconnect();
}
void CloudPolicyInvalidatorTestBase::StartInvalidator() {
invalidator_ = std::make_unique<CloudPolicyInvalidator>(
GetPolicyInvalidationScope(), &invalidation_listener_, &core_,
task_environment_.GetMainThreadTaskRunner(),
task_environment_.GetMockClock(), kDeviceLocalAccountId);
}
void CloudPolicyInvalidatorTestBase::ConnectCore() {
std::unique_ptr<testing::NiceMock<MockCloudPolicyClient>> client =
std::make_unique<testing::NiceMock<MockCloudPolicyClient>>();
ON_CALL(*client, FetchPolicy(PolicyFetchReason::kInvalidation))
.WillByDefault([this]() { ++policy_refresh_count_; });
client->SetDMToken("dm");
core_.Connect(std::move(client));
}
void CloudPolicyInvalidatorTestBase::StartRefreshScheduler() {
core_.StartRefreshScheduler();
}
void CloudPolicyInvalidatorTestBase::DisconnectCore() {
core_.Disconnect();
}
void CloudPolicyInvalidatorTestBase::StorePolicy(int64_t invalidation_version,
bool policy_changed,
const base::Time& time) {
auto data = std::make_unique<em::PolicyData>();
data->set_timestamp(time.InMillisecondsSinceUnixEpoch());
// Swap the policy value if a policy change is desired.
if (policy_changed) {
policy_value_cur_ =
policy_value_cur_ == kPolicyValueA ? kPolicyValueB : kPolicyValueA;
}
data->set_policy_value(policy_value_cur_);
store_.invalidation_version_ = invalidation_version;
store_.set_policy_data_for_testing(std::move(data));
base::Value::Dict policies;
policies.Set(key::kMaxInvalidationFetchDelay,
static_cast<int>(
CloudPolicyInvalidator::kMaxFetchDelayMin.InMilliseconds()));
store_.policy_map_.LoadFrom(policies, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD);
store_.NotifyStoreLoaded();
}
void CloudPolicyInvalidatorTestBase::DisableInvalidationListener() {
invalidation_listener_.Shutdown();
}
void CloudPolicyInvalidatorTestBase::EnableInvalidationListener() {
invalidation_listener_.Start();
}
invalidation::DirectInvalidation
CloudPolicyInvalidatorTestBase::FireInvalidation(int64_t version,
const std::string& payload) {
invalidation::DirectInvalidation invalidation(GetPolicyInvalidationType(),
version, payload);
invalidation_listener_.FireInvalidation(invalidation);
return invalidation;
}
bool CloudPolicyInvalidatorTestBase::ClientInvalidationInfoIsUnset() {
MockCloudPolicyClient* client =
static_cast<MockCloudPolicyClient*>(core_.client());
if (!client) {
return false;
}
return client->invalidation_version_ == 0 &&
client->invalidation_payload_.empty();
}
int CloudPolicyInvalidatorTestBase::GetPolicyRefreshCountAndReset() {
return std::exchange(policy_refresh_count_, 0);
}
bool CloudPolicyInvalidatorTestBase::ClientInvalidationInfoMatches(
const invalidation::DirectInvalidation& invalidation) {
MockCloudPolicyClient* client =
static_cast<MockCloudPolicyClient*>(core_.client());
if (!client) {
return false;
}
return invalidation.version() == client->invalidation_version_ &&
invalidation.payload() == client->invalidation_payload_;
}
bool CloudPolicyInvalidatorTestBase::InvalidationsEnabled() {
return core_.refresh_scheduler()->invalidations_available();
}
bool CloudPolicyInvalidatorTestBase::IsInvalidatorRegistered() {
return invalidator_ && invalidation_listener_.HasObserver(invalidator_.get());
}
void CloudPolicyInvalidatorTestBase::FastForwardBy(base::TimeDelta delta) {
task_environment_.FastForwardBy(delta);
}
void CloudPolicyInvalidatorTestBase::FastForwardByInvalidationDelay() {
const auto* delay_policy_value = store_.policy_map().GetValue(
key::kMaxInvalidationFetchDelay, base::Value::Type::INTEGER);
const base::TimeDelta max_delay =
delay_policy_value ? base::Milliseconds(delay_policy_value->GetInt())
: CloudPolicyInvalidator::kMaxFetchDelayMax;
FastForwardBy(max_delay);
}
base::Time CloudPolicyInvalidatorTestBase::Now() {
return task_environment_.GetMockClock()->Now();
}
int64_t CloudPolicyInvalidatorTestBase::V(int version) {
return GetVersion(start_time) + version;
}
int64_t CloudPolicyInvalidatorTestBase::GetVersion(base::Time time) {
return (time - base::Time::UnixEpoch()).InMicroseconds();
}
PolicyInvalidationScope
CloudPolicyInvalidatorTestBase::GetPolicyInvalidationScope() const {
return PolicyInvalidationScope::kUser;
}
std::string CloudPolicyInvalidatorTestBase::GetPolicyInvalidationType() const {
switch (GetPolicyInvalidationScope()) {
case PolicyInvalidationScope::kUser:
return "USER_POLICY_FETCH";
case PolicyInvalidationScope::kDevice:
return "DEVICE_POLICY_FETCH";
case PolicyInvalidationScope::kDeviceLocalAccount:
return "PUBLIC_ACCOUNT_POLICY_FETCH-test_account";
case PolicyInvalidationScope::kCBCM:
return "BROWSER_POLICY_FETCH";
}
}
class CloudPolicyInvalidatorTest : public CloudPolicyInvalidatorTestBase {};
TEST_F(CloudPolicyInvalidatorTest, DoesNotRegisterWhenCoreIsNotConnected) {
StartInvalidator();
EnableInvalidationListener();
FireInvalidation(V(1), "test");
FastForwardByInvalidationDelay();
EXPECT_FALSE(IsInvalidatorRegistered());
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 0);
}
TEST_F(CloudPolicyInvalidatorTest,
DoesNotRegisterWhenRefreshSchedulerNotStarted) {
StartInvalidator();
EnableInvalidationListener();
ConnectCore();
FireInvalidation(V(1), "test");
FastForwardByInvalidationDelay();
EXPECT_FALSE(IsInvalidatorRegistered());
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 0);
}
TEST_F(CloudPolicyInvalidatorTest, RegistersWhenCoreIsReady) {
StartInvalidator();
EnableInvalidationListener();
ConnectCore();
StartRefreshScheduler();
FireInvalidation(V(1), "test");
FastForwardByInvalidationDelay();
EXPECT_TRUE(IsInvalidatorRegistered());
EXPECT_TRUE(InvalidationsEnabled());
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
}
TEST_F(CloudPolicyInvalidatorTest,
UnregistersWhenCoreDisconnectsAndRegistersWhenConnected) {
StartInvalidator();
EnableInvalidationListener();
ConnectCore();
StartRefreshScheduler();
EXPECT_TRUE(IsInvalidatorRegistered());
EXPECT_TRUE(InvalidationsEnabled());
DisconnectCore();
EXPECT_FALSE(IsInvalidatorRegistered());
ConnectCore();
EXPECT_FALSE(IsInvalidatorRegistered());
StartRefreshScheduler();
EXPECT_TRUE(IsInvalidatorRegistered());
EXPECT_TRUE(InvalidationsEnabled());
}
TEST_F(CloudPolicyInvalidatorTest,
UpdatesInvalidationStatusWhenInvalidationListenerStarts) {
StartInvalidator();
ConnectCore();
StartRefreshScheduler();
EXPECT_TRUE(IsInvalidatorRegistered());
EXPECT_FALSE(InvalidationsEnabled());
EnableInvalidationListener();
EXPECT_TRUE(IsInvalidatorRegistered());
EXPECT_TRUE(InvalidationsEnabled());
DisableInvalidationListener();
EXPECT_TRUE(IsInvalidatorRegistered());
EXPECT_FALSE(InvalidationsEnabled());
}
TEST_F(CloudPolicyInvalidatorTest, HandlesInvalidation) {
StorePolicy();
StartInvalidator();
ConnectCore();
StartRefreshScheduler();
EnableInvalidationListener();
const invalidation::DirectInvalidation inv =
FireInvalidation(V(12), "test_payload");
// Verify that invalidation is not yet handled as we did not pass random
// delay.
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 0);
FastForwardByInvalidationDelay();
// Verify that the invalidation is handled after the delay.
EXPECT_TRUE(ClientInvalidationInfoMatches(inv));
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
StorePolicy(V(12));
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(V(12), invalidator()->highest_handled_invalidation_version());
}
TEST_F(CloudPolicyInvalidatorTest, HandlesInvalidationWithoutPayload) {
StorePolicy();
StartInvalidator();
ConnectCore();
StartRefreshScheduler();
EnableInvalidationListener();
// Fire an invalidation and check that it triggered a policy refresh only
// after a random delay.
const invalidation::DirectInvalidation inv = FireInvalidation(V(12), "");
// Verify that invalidation is not yet handled as we did not pass random
// delay.
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 0);
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
FastForwardByInvalidationDelay();
EXPECT_TRUE(ClientInvalidationInfoMatches(inv));
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
StorePolicy(V(12));
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(V(12), invalidator()->highest_handled_invalidation_version());
}
TEST_F(CloudPolicyInvalidatorTest, HandlesInvalidationBeforePolicyLoaded) {
StartInvalidator();
ConnectCore();
StartRefreshScheduler();
EnableInvalidationListener();
const invalidation::DirectInvalidation inv =
FireInvalidation(V(12), "test_payload");
FastForwardByInvalidationDelay();
EXPECT_TRUE(ClientInvalidationInfoMatches(inv));
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
StorePolicy(V(12));
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(V(12), invalidator()->highest_handled_invalidation_version());
}
TEST_F(CloudPolicyInvalidatorTest, HandlesMultipleInvalidations) {
StorePolicy();
StartInvalidator();
ConnectCore();
StartRefreshScheduler();
EnableInvalidationListener();
// Fire invalidations out of order.
const invalidation::DirectInvalidation inv2 = FireInvalidation(V(2), "test1");
const invalidation::DirectInvalidation inv1 = FireInvalidation(V(1), "test2");
const invalidation::DirectInvalidation inv3 = FireInvalidation(V(3), "test3");
FastForwardByInvalidationDelay();
// Make sure the policy is refreshed once.
EXPECT_TRUE(ClientInvalidationInfoMatches(inv3));
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
// Make sure that the invalidation data is only removed from the client after
// the store is loaded with the latest version.
StorePolicy(V(1));
EXPECT_TRUE(ClientInvalidationInfoMatches(inv3));
EXPECT_EQ(V(1), invalidator()->highest_handled_invalidation_version());
StorePolicy(V(2));
EXPECT_TRUE(ClientInvalidationInfoMatches(inv3));
EXPECT_EQ(V(2), invalidator()->highest_handled_invalidation_version());
StorePolicy(V(3));
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(V(3), invalidator()->highest_handled_invalidation_version());
}
TEST_F(CloudPolicyInvalidatorTest, IgnoresOldInvalidations) {
StorePolicy();
ConnectCore();
StartRefreshScheduler();
EnableInvalidationListener();
StartInvalidator();
const invalidation::DirectInvalidation inv0 = FireInvalidation(V(2), "test2");
FastForwardByInvalidationDelay();
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
EXPECT_TRUE(ClientInvalidationInfoMatches(inv0));
StorePolicy(V(2));
EXPECT_EQ(V(2), invalidator()->highest_handled_invalidation_version());
// Check that an invalidation whose version is lower than the highest handled
// so far is acknowledged but ignored otherwise.
const invalidation::DirectInvalidation inv1 = FireInvalidation(V(1), "test1");
FastForwardByInvalidationDelay();
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 0);
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(V(2), invalidator()->highest_handled_invalidation_version());
// Check that an invalidation whose version matches the highest handled so far
// is acknowledged but ignored otherwise.
const invalidation::DirectInvalidation inv2 = FireInvalidation(V(2), "test2");
FastForwardByInvalidationDelay();
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 0);
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(V(2), invalidator()->highest_handled_invalidation_version());
// Check that an invalidation whose version is higher than the highest handled
// so far is handled, causing a policy refresh.
const invalidation::DirectInvalidation inv3 = FireInvalidation(V(3), "test3");
FastForwardByInvalidationDelay();
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
EXPECT_TRUE(ClientInvalidationInfoMatches(inv3));
StorePolicy(V(3));
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(V(3), invalidator()->highest_handled_invalidation_version());
}
TEST_F(CloudPolicyInvalidatorTest, NoticesRegularPolicyRefresh) {
StorePolicy();
ConnectCore();
StartRefreshScheduler();
EnableInvalidationListener();
StartInvalidator();
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
StorePolicy(V(2));
EXPECT_EQ(V(2), invalidator()->highest_handled_invalidation_version());
}
class CloudPolicyInvalidatorOwnerNameTest
: public CloudPolicyInvalidatorTestBase {
protected:
PolicyInvalidationScope GetPolicyInvalidationScope() const override {
return scope_;
}
PolicyInvalidationScope scope_;
};
TEST_F(CloudPolicyInvalidatorOwnerNameTest, GetTypeForUserScope) {
scope_ = PolicyInvalidationScope::kUser;
StartInvalidator();
ASSERT_TRUE(invalidator());
EXPECT_EQ("USER_POLICY_FETCH", invalidator()->GetType());
}
TEST_F(CloudPolicyInvalidatorOwnerNameTest, GetTypeForDeviceScope) {
scope_ = PolicyInvalidationScope::kDevice;
StartInvalidator();
ASSERT_TRUE(invalidator());
EXPECT_EQ("DEVICE_POLICY_FETCH", invalidator()->GetType());
}
TEST_F(CloudPolicyInvalidatorOwnerNameTest, GetTypeForDeviceLocalAccountScope) {
scope_ = PolicyInvalidationScope::kDeviceLocalAccount;
StartInvalidator();
ASSERT_TRUE(invalidator());
EXPECT_EQ("PUBLIC_ACCOUNT_POLICY_FETCH-test_account",
invalidator()->GetType());
}
TEST_F(CloudPolicyInvalidatorOwnerNameTest, GetTypeForCbcmScope) {
scope_ = PolicyInvalidationScope::kCBCM;
StartInvalidator();
ASSERT_TRUE(invalidator());
EXPECT_EQ("BROWSER_POLICY_FETCH", invalidator()->GetType());
}
class CloudPolicyInvalidatorUserTypedTest
: public CloudPolicyInvalidatorTestBase,
public testing::WithParamInterface<PolicyInvalidationScope> {
public:
CloudPolicyInvalidatorUserTypedTest(
const CloudPolicyInvalidatorUserTypedTest&) = delete;
CloudPolicyInvalidatorUserTypedTest& operator=(
const CloudPolicyInvalidatorUserTypedTest&) = delete;
protected:
CloudPolicyInvalidatorUserTypedTest();
base::HistogramBase::Count32 GetCount(MetricPolicyRefresh metric);
base::HistogramBase::Count32 GetInvalidationCount(
PolicyInvalidationType type);
private:
// CloudPolicyInvalidatorTest:
PolicyInvalidationScope GetPolicyInvalidationScope() const override;
base::HistogramTester histogram_tester_;
};
CloudPolicyInvalidatorUserTypedTest::CloudPolicyInvalidatorUserTypedTest() {
ConnectCore();
StartRefreshScheduler();
}
base::HistogramBase::Count32 CloudPolicyInvalidatorUserTypedTest::GetCount(
MetricPolicyRefresh metric) {
const char* metric_name = CloudPolicyInvalidator::GetPolicyRefreshMetricName(
GetPolicyInvalidationScope());
return histogram_tester_.GetHistogramSamplesSinceCreation(metric_name)
->GetCount(metric);
}
base::HistogramBase::Count32
CloudPolicyInvalidatorUserTypedTest::GetInvalidationCount(
PolicyInvalidationType type) {
const char* metric_name =
CloudPolicyInvalidator::GetPolicyInvalidationMetricName(
GetPolicyInvalidationScope());
return histogram_tester_.GetHistogramSamplesSinceCreation(metric_name)
->GetCount(type);
}
PolicyInvalidationScope
CloudPolicyInvalidatorUserTypedTest::GetPolicyInvalidationScope() const {
return GetParam();
}
TEST_P(CloudPolicyInvalidatorUserTypedTest,
RefreshMetricsInvalidationsDisabled) {
StartInvalidator();
DisableInvalidationListener();
StorePolicy(0, /*policy_changed=*/false);
StorePolicy(0, /*policy_changed=*/true);
EXPECT_EQ(0, GetCount(METRIC_POLICY_REFRESH_CHANGED));
EXPECT_EQ(2, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS));
EXPECT_EQ(0, GetCount(METRIC_POLICY_REFRESH_UNCHANGED));
EXPECT_EQ(0, GetCount(METRIC_POLICY_REFRESH_INVALIDATED_CHANGED));
EXPECT_EQ(0, GetCount(METRIC_POLICY_REFRESH_INVALIDATED_UNCHANGED));
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
}
TEST_P(CloudPolicyInvalidatorUserTypedTest, RefreshMetricsNoInvalidations) {
// Store loads should be differentiated depending on whether the invalidation
// service was enabled or not.
StorePolicy();
StartInvalidator();
EnableInvalidationListener();
// Initially, invalidations have not been enabled past the grace period, so
// invalidations are OFF.
StorePolicy(0, /*policy_changed=*/false);
StorePolicy(0, /*policy_changed=*/true);
EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_UNCHANGED));
EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS));
// If the clock advances less than the grace period, invalidations are OFF.
FastForwardBy(base::Seconds(1));
StorePolicy(0, /*policy_changed=*/false);
StorePolicy(0, /*policy_changed=*/true);
EXPECT_EQ(2, GetCount(METRIC_POLICY_REFRESH_UNCHANGED));
EXPECT_EQ(2, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS));
// After the grace period elapses, invalidations are ON.
FastForwardBy(CloudPolicyInvalidator::kInvalidationGracePeriod);
StorePolicy(0, /*policy_changed=*/false);
StorePolicy(0, /*policy_changed=*/true);
EXPECT_EQ(3, GetCount(METRIC_POLICY_REFRESH_UNCHANGED));
EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_CHANGED));
// After the invalidation service is disabled, invalidations are OFF.
DisableInvalidationListener();
StorePolicy(0, /*policy_changed=*/false);
StorePolicy(0, /*policy_changed=*/true);
EXPECT_EQ(4, GetCount(METRIC_POLICY_REFRESH_UNCHANGED));
EXPECT_EQ(3, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS));
// Enabling the invalidation service results in a new grace period, so
// invalidations are OFF.
EnableInvalidationListener();
StorePolicy(0, /*policy_changed=*/false);
StorePolicy(0, /*policy_changed=*/true);
EXPECT_EQ(5, GetCount(METRIC_POLICY_REFRESH_UNCHANGED));
EXPECT_EQ(4, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS));
// After the grace period elapses, invalidations are ON.
FastForwardBy(CloudPolicyInvalidator::kInvalidationGracePeriod);
StorePolicy(0, /*policy_changed=*/false);
StorePolicy(0, /*policy_changed=*/true);
EXPECT_EQ(2, GetCount(METRIC_POLICY_REFRESH_CHANGED));
EXPECT_EQ(4, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS));
EXPECT_EQ(6, GetCount(METRIC_POLICY_REFRESH_UNCHANGED));
EXPECT_EQ(0, GetCount(METRIC_POLICY_REFRESH_INVALIDATED_CHANGED));
EXPECT_EQ(0, GetCount(METRIC_POLICY_REFRESH_INVALIDATED_UNCHANGED));
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
}
TEST_P(CloudPolicyInvalidatorUserTypedTest, RefreshMetricsInvalidation) {
StartInvalidator();
EnableInvalidationListener();
StorePolicy();
EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS));
FastForwardBy(CloudPolicyInvalidator::kInvalidationGracePeriod);
FireInvalidation(V(5), "test");
FastForwardByInvalidationDelay();
StorePolicy(0, /*policy_changed=*/false);
EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_UNCHANGED));
StorePolicy(0, /*policy_changed=*/true);
EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_CHANGED));
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
StorePolicy(V(5), true /* policy_changed */);
EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_INVALIDATED_CHANGED));
EXPECT_EQ(V(5), invalidator()->highest_handled_invalidation_version());
// Store loads after the invalidation is complete are not counted as
// invalidated.
StorePolicy(V(5), /*policy_changed=*/false);
StorePolicy(V(6), /*policy_changed=*/true);
StorePolicy(V(6), /*policy_changed=*/false);
StorePolicy(V(7), /*policy_changed=*/true);
StorePolicy(V(7), /*policy_changed=*/false);
StorePolicy(V(8), /*policy_changed=*/true);
StorePolicy(V(8), /*policy_changed=*/false);
EXPECT_EQ(4, GetCount(METRIC_POLICY_REFRESH_CHANGED));
EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS));
EXPECT_EQ(5, GetCount(METRIC_POLICY_REFRESH_UNCHANGED));
EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_INVALIDATED_CHANGED));
EXPECT_EQ(0, GetCount(METRIC_POLICY_REFRESH_INVALIDATED_UNCHANGED));
EXPECT_EQ(V(8), invalidator()->highest_handled_invalidation_version());
}
TEST_P(CloudPolicyInvalidatorUserTypedTest, ExpiredInvalidations) {
FastForwardBy(base::Hours(1));
const auto policy_store_timestamp = Now();
StorePolicy(0, false, policy_store_timestamp);
StartInvalidator();
EnableInvalidationListener();
// Invalidations fired before the last fetch time (adjusted by max time delta)
// should be ignored (and count as expired).
const base::Time expired_invalidation_timestamp =
policy_store_timestamp -
invalidation_timeouts::kMaxInvalidationTimeDelta - base::Seconds(1);
// Fire expired invalidation with a payload.
invalidation::DirectInvalidation inv =
FireInvalidation(GetVersion(expired_invalidation_timestamp), "test");
FastForwardByInvalidationDelay();
EXPECT_TRUE(ClientInvalidationInfoIsUnset());
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 0);
// Fire expired invalidation without a payload.
inv = FireInvalidation(GetVersion(expired_invalidation_timestamp), "");
FastForwardByInvalidationDelay();
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 0);
// Invalidations fired after the last fetch should not be ignored.
const base::Time non_expired_invalidaiton_timestamp =
expired_invalidation_timestamp + base::Seconds(1);
// Fire a fine invalidation without a payload.
inv = FireInvalidation(GetVersion(non_expired_invalidaiton_timestamp), "");
FastForwardByInvalidationDelay();
EXPECT_TRUE(ClientInvalidationInfoMatches(inv));
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
// Fire three fine invalidations with a payload.
inv = FireInvalidation(
GetVersion(non_expired_invalidaiton_timestamp + base::Minutes(10)),
"test");
FastForwardByInvalidationDelay();
EXPECT_TRUE(ClientInvalidationInfoMatches(inv));
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
inv = FireInvalidation(
GetVersion(non_expired_invalidaiton_timestamp + base::Minutes(20)),
"test");
FastForwardByInvalidationDelay();
EXPECT_TRUE(ClientInvalidationInfoMatches(inv));
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
inv = FireInvalidation(
GetVersion(non_expired_invalidaiton_timestamp + base::Minutes(30)),
"test");
FastForwardByInvalidationDelay();
EXPECT_TRUE(ClientInvalidationInfoMatches(inv));
EXPECT_EQ(GetPolicyRefreshCountAndReset(), 1);
// Verify that received invalidations metrics are correct.
EXPECT_EQ(1, GetInvalidationCount(POLICY_INVALIDATION_TYPE_NO_PAYLOAD));
EXPECT_EQ(3, GetInvalidationCount(POLICY_INVALIDATION_TYPE_NORMAL));
EXPECT_EQ(1,
GetInvalidationCount(POLICY_INVALIDATION_TYPE_NO_PAYLOAD_EXPIRED));
EXPECT_EQ(1, GetInvalidationCount(POLICY_INVALIDATION_TYPE_EXPIRED));
// New policies never stored, verify invalidation handling did not finished.
EXPECT_EQ(0, invalidator()->highest_handled_invalidation_version());
}
INSTANTIATE_TEST_SUITE_P(
CloudPolicyInvalidatorUserTypedTestInstance,
CloudPolicyInvalidatorUserTypedTest,
testing::Values(PolicyInvalidationScope::kUser,
PolicyInvalidationScope::kDevice,
PolicyInvalidationScope::kDeviceLocalAccount));
} // namespace policy