| // 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 |