| // Copyright 2013 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 "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/ref_counted.h" |
| #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 "build/build_config.h" |
| #include "chrome/browser/policy/cloud/user_cloud_policy_invalidator.h" |
| #include "components/invalidation/impl/fake_invalidation_service.h" |
| #include "components/invalidation/impl/invalidator_registrar_with_memory.h" |
| #include "components/invalidation/public/invalidation_util.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/cloud_policy_validator.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" |
| |
| namespace em = enterprise_management; |
| |
| namespace policy { |
| |
| namespace { |
| // Invalidation topics which can be used in tests. |
| constexpr auto kNoTopic = std::nullopt; |
| const invalidation::Topic kTopicA = "topic_a"; |
| const invalidation::Topic kTopicB = "topic_b"; |
| } // namespace |
| |
| class CloudPolicyInvalidatorTestBase : public testing::Test { |
| protected: |
| CloudPolicyInvalidatorTestBase(); |
| |
| void TearDown() override; |
| |
| // Starts the invalidator which will be tested. |
| // |initialize| determines if the invalidator should be initialized. |
| // |start_refresh_scheduler| determines if the refresh scheduler should start. |
| // |highest_handled_invalidation_version| is the highest invalidation version |
| // that was handled already before this invalidator was created. |
| void StartInvalidator(bool initialize, |
| bool start_refresh_scheduler, |
| int64_t highest_handled_invalidation_version); |
| void StartInvalidator() { |
| StartInvalidator(true, /* initialize */ |
| true, /* start_refresh_scheduler */ |
| 0 /* highest_handled_invalidation_version */); |
| } |
| |
| const CloudPolicyInvalidator* invalidator() const { |
| return invalidator_.get(); |
| } |
| |
| // Calls Initialize on the invalidator. |
| void InitializeInvalidator(); |
| |
| // Calls Shutdown on the invalidator. Test must call DestroyInvalidator |
| // afterwards to prevent Shutdown from being called twice. |
| void ShutdownInvalidator(); |
| |
| // Destroys the invalidator. |
| void DestroyInvalidator(); |
| |
| // 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. |
| // |topic| determines which policy topic the store will report the |
| // invalidator should register for. May be kNoTopic for no topic. |
| // |invalidation_version| determines what invalidation the store will report. |
| // |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(const std::optional<invalidation::Topic>& topic, |
| int64_t invalidation_version, |
| bool policy_changed, |
| const base::Time& time); |
| void StorePolicy(const std::optional<invalidation::Topic>& topic, |
| int64_t invalidation_version, |
| bool policy_changed) { |
| StorePolicy(topic, invalidation_version, policy_changed, |
| Now() - base::Minutes(5)); |
| } |
| void StorePolicy(const std::optional<invalidation::Topic>& topic, |
| int64_t invalidation_version) { |
| StorePolicy(topic, invalidation_version, false); |
| } |
| void StorePolicy(const std::optional<invalidation::Topic>& topic) { |
| StorePolicy(topic, 0); |
| } |
| |
| // Disables the invalidation service. It is enabled by default. |
| void DisableInvalidationService(); |
| |
| // Enables the invalidation service. It is enabled by default. |
| void EnableInvalidationService(); |
| |
| // Causes the invalidation service to fire an invalidation. |
| invalidation::Invalidation FireInvalidation(const invalidation::Topic& topic, |
| 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::Invalidation& invalidation); |
| |
| // Returns true if the invalidation info of the `core_`'s client is unset. |
| bool ClientInvalidationInfoIsUnset(); |
| |
| // Checks that the policy was not refreshed due to an invalidation. |
| bool CheckPolicyNotRefreshed(); |
| |
| // Checks that the policy was refreshed due to an invalidation with the given |
| // base delay. |
| bool CheckPolicyRefreshed(base::TimeDelta delay = base::TimeDelta()); |
| |
| // 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(); |
| |
| // Determines if the invalidator has registered for a topic with the |
| // invalidation service. |
| // This implicitly also checks that that invalidator is registered as an |
| // observer (otherwise, it could not have registered topics). |
| bool IsInvalidatorRegistered(const invalidation::Topic& topic); |
| |
| // Returns the highest invalidation version that was handled already according |
| // to the |invalidator_|. |
| int64_t GetHighestHandledInvalidationVersion() const; |
| |
| // Advance the test clock. |
| void AdvanceClock(base::TimeDelta delta); |
| |
| // 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 current 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; |
| |
| private: |
| // Checks that the policy was refreshed the given number of times. |
| bool CheckPolicyRefreshCount(int count); |
| |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| |
| // Objects the invalidator depends on. |
| invalidation::FakeInvalidationService invalidation_service_; |
| MockCloudPolicyStore store_; |
| CloudPolicyCore core_; |
| raw_ptr<MockCloudPolicyClient, DanglingUntriaged> client_; |
| scoped_refptr<base::TestSimpleTaskRunner> task_runner_; |
| base::SimpleTestClock clock_; |
| |
| // The invalidator which will be tested. |
| std::unique_ptr<CloudPolicyInvalidator> invalidator_; |
| |
| // Topics for the test policy objects. |
| invalidation::Topic topic_a_; |
| invalidation::Topic topic_b_; |
| |
| // Fake policy values which are alternated to cause the store to report a |
| // changed policy. |
| const std::string policy_value_a_; |
| const std::string policy_value_b_; |
| |
| // The currently used policy value. |
| std::string policy_value_cur_; |
| |
| const char* account_id_ = "test_account"; |
| }; |
| |
| CloudPolicyInvalidatorTestBase::CloudPolicyInvalidatorTestBase() |
| : core_(dm_protocol::kChromeUserPolicyType, |
| std::string(), |
| &store_, |
| task_environment_.GetMainThreadTaskRunner(), |
| network::TestNetworkConnectionTracker::CreateGetter()), |
| client_(nullptr), |
| task_runner_(new base::TestSimpleTaskRunner()), |
| policy_value_a_(kTopicA), |
| policy_value_b_(kTopicB), |
| policy_value_cur_(policy_value_a_) { |
| clock_.SetNow(base::Time::UnixEpoch() + base::Seconds(987654321)); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::TearDown() { |
| if (invalidator_) |
| invalidator_->Shutdown(); |
| core_.Disconnect(); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::StartInvalidator( |
| bool initialize, |
| bool start_refresh_scheduler, |
| int64_t highest_handled_invalidation_version) { |
| invalidator_ = std::make_unique<CloudPolicyInvalidator>( |
| GetPolicyInvalidationScope(), &core_, task_runner_, &clock_, |
| highest_handled_invalidation_version, account_id_); |
| if (start_refresh_scheduler) { |
| ConnectCore(); |
| StartRefreshScheduler(); |
| } |
| if (initialize) |
| InitializeInvalidator(); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::InitializeInvalidator() { |
| invalidator_->Initialize(&invalidation_service_); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::ShutdownInvalidator() { |
| invalidator_->Shutdown(); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::DestroyInvalidator() { |
| invalidator_.reset(); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::ConnectCore() { |
| client_ = new MockCloudPolicyClient(); |
| client_->SetDMToken("dm"); |
| core_.Connect(std::unique_ptr<CloudPolicyClient>(client_)); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::StartRefreshScheduler() { |
| core_.StartRefreshScheduler(); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::DisconnectCore() { |
| client_ = nullptr; |
| core_.Disconnect(); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::StorePolicy( |
| const std::optional<invalidation::Topic>& topic, |
| int64_t invalidation_version, |
| bool policy_changed, |
| const base::Time& time) { |
| auto data = std::make_unique<em::PolicyData>(); |
| if (topic.has_value()) { |
| // CloudPolicyInvalidator expects the topic to subscribe in this field. |
| data->set_policy_invalidation_topic(topic.value()); |
| } |
| data->set_timestamp(time.InMillisecondsSinceUnixEpoch()); |
| // Swap the policy value if a policy change is desired. |
| if (policy_changed) |
| policy_value_cur_ = policy_value_cur_ == policy_value_a_ ? |
| policy_value_b_ : policy_value_a_; |
| 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, |
| CloudPolicyInvalidator::kMaxFetchDelayMin); |
| store_.policy_map_.LoadFrom(policies, POLICY_LEVEL_MANDATORY, |
| POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD); |
| store_.NotifyStoreLoaded(); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::DisableInvalidationService() { |
| invalidation_service_.SetInvalidatorState( |
| invalidation::InvalidatorState::kDisabled); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::EnableInvalidationService() { |
| invalidation_service_.SetInvalidatorState( |
| invalidation::InvalidatorState::kEnabled); |
| } |
| |
| invalidation::Invalidation CloudPolicyInvalidatorTestBase::FireInvalidation( |
| const invalidation::Topic& topic, |
| int64_t version, |
| const std::string& payload) { |
| invalidation::Invalidation invalidation = |
| invalidation::Invalidation(topic, version, payload); |
| invalidation_service_.EmitInvalidationForTest(invalidation); |
| return invalidation; |
| } |
| |
| bool CloudPolicyInvalidatorTestBase::ClientInvalidationInfoIsUnset() { |
| MockCloudPolicyClient* client = |
| static_cast<MockCloudPolicyClient*>(core_.client()); |
| return client->invalidation_version_ == 0 && |
| client->invalidation_payload_.empty(); |
| } |
| |
| bool CloudPolicyInvalidatorTestBase::ClientInvalidationInfoMatches( |
| const invalidation::Invalidation& invalidation) { |
| MockCloudPolicyClient* client = |
| static_cast<MockCloudPolicyClient*>(core_.client()); |
| return invalidation.version() == client->invalidation_version_ && |
| invalidation.payload() == client->invalidation_payload_; |
| } |
| |
| bool CloudPolicyInvalidatorTestBase::CheckPolicyNotRefreshed() { |
| return CheckPolicyRefreshCount(0); |
| } |
| |
| bool CloudPolicyInvalidatorTestBase::InvalidationsEnabled() { |
| return core_.refresh_scheduler()->invalidations_available(); |
| } |
| |
| bool CloudPolicyInvalidatorTestBase::IsInvalidatorRegistered() { |
| return invalidation_service_.invalidator_registrar().HasObserver( |
| invalidator_.get()); |
| } |
| |
| bool CloudPolicyInvalidatorTestBase::IsInvalidatorRegistered( |
| const invalidation::Topic& topic) { |
| return invalidation_service_.invalidator_registrar() |
| .GetRegisteredTopics(invalidator_.get()) |
| .contains(topic); |
| } |
| |
| int64_t CloudPolicyInvalidatorTestBase::GetHighestHandledInvalidationVersion() |
| const { |
| return invalidator_->highest_handled_invalidation_version(); |
| } |
| |
| void CloudPolicyInvalidatorTestBase::AdvanceClock(base::TimeDelta delta) { |
| clock_.Advance(delta); |
| } |
| |
| base::Time CloudPolicyInvalidatorTestBase::Now() { |
| return clock_.Now(); |
| } |
| |
| int64_t CloudPolicyInvalidatorTestBase::V(int version) { |
| return GetVersion(Now()) + version; |
| } |
| |
| int64_t CloudPolicyInvalidatorTestBase::GetVersion(base::Time time) { |
| return (time - base::Time::UnixEpoch()).InMicroseconds(); |
| } |
| |
| PolicyInvalidationScope |
| CloudPolicyInvalidatorTestBase::GetPolicyInvalidationScope() const { |
| return PolicyInvalidationScope::kUser; |
| } |
| |
| bool CloudPolicyInvalidatorTestBase::CheckPolicyRefreshed( |
| base::TimeDelta delay) { |
| base::TimeDelta max_delay = |
| delay + base::Milliseconds(CloudPolicyInvalidator::kMaxFetchDelayMin); |
| |
| if (!task_runner_->HasPendingTask()) |
| return false; |
| base::TimeDelta actual_delay = task_runner_->FinalPendingTaskDelay(); |
| EXPECT_GE(actual_delay, delay); |
| EXPECT_LE(actual_delay, max_delay); |
| |
| return CheckPolicyRefreshCount(1); |
| } |
| |
| bool CloudPolicyInvalidatorTestBase::CheckPolicyRefreshCount(int count) { |
| if (!client_) { |
| task_runner_->RunUntilIdle(); |
| return count == 0; |
| } |
| |
| // Clear any non-invalidation refreshes which may be pending. |
| EXPECT_CALL(*client_, FetchPolicy(testing::_)).Times(testing::AnyNumber()); |
| base::RunLoop().RunUntilIdle(); |
| testing::Mock::VerifyAndClearExpectations(client_); |
| |
| // Run the invalidator tasks then check for invalidation refreshes. |
| EXPECT_CALL(*client_, FetchPolicy(PolicyFetchReason::kInvalidation)) |
| .Times(count); |
| task_runner_->RunUntilIdle(); |
| base::RunLoop().RunUntilIdle(); |
| return testing::Mock::VerifyAndClearExpectations(client_); |
| } |
| |
| class CloudPolicyInvalidatorTest : public CloudPolicyInvalidatorTestBase {}; |
| |
| TEST_F(CloudPolicyInvalidatorTest, Uninitialized) { |
| // No invalidations should be processed if the invalidator is not initialized. |
| StartInvalidator(false, /* initialize */ |
| true, /* start_refresh_scheduler */ |
| 0 /* highest_handled_invalidation_version*/); |
| StorePolicy(kTopicA); |
| EXPECT_FALSE(IsInvalidatorRegistered()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, RefreshSchedulerNotStarted) { |
| // No invalidations should be processed if the refresh scheduler is not |
| // started. |
| StartInvalidator(true, /* initialize */ |
| false, /* start_refresh_scheduler */ |
| 0 /* highest_handled_invalidation_version*/); |
| StorePolicy(kTopicA); |
| EXPECT_FALSE(IsInvalidatorRegistered()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, DisconnectCoreThenInitialize) { |
| // No invalidations should be processed if the core is disconnected before |
| // initialization. |
| StartInvalidator(false, /* initialize */ |
| true, /* start_refresh_scheduler */ |
| 0 /* highest_handled_invalidation_version*/); |
| DisconnectCore(); |
| InitializeInvalidator(); |
| StorePolicy(kTopicA); |
| EXPECT_FALSE(IsInvalidatorRegistered()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, InitializeThenStartRefreshScheduler) { |
| // Make sure registration occurs and invalidations are processed when |
| // Initialize is called before starting the refresh scheduler. |
| // Note that the reverse case (start refresh scheduler then initialize) is |
| // the default behavior for the test fixture, so will be tested in most other |
| // tests. |
| StartInvalidator(true, /* initialize */ |
| false, /* start_refresh_scheduler */ |
| 0 /* highest_handled_invalidation_version*/); |
| ConnectCore(); |
| StartRefreshScheduler(); |
| StorePolicy(kTopicA); |
| EXPECT_TRUE(IsInvalidatorRegistered(kTopicA)); |
| FireInvalidation(kTopicA, V(1), "test"); |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, RegisterOnStoreLoaded) { |
| // No registration when store is not loaded. |
| StartInvalidator(); |
| EXPECT_FALSE(IsInvalidatorRegistered()); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| |
| // No registration when store is loaded with no invalidation topic id. |
| StorePolicy(kNoTopic); |
| EXPECT_FALSE(IsInvalidatorRegistered()); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| |
| // Check registration when store is loaded for topic A. |
| StorePolicy(kTopicA); |
| EXPECT_TRUE(IsInvalidatorRegistered(kTopicA)); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| FireInvalidation(kTopicA, V(5), "test"); |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, ChangeRegistration) { |
| // Register for topic A. |
| StartInvalidator(); |
| StorePolicy(kTopicA); |
| EXPECT_TRUE(IsInvalidatorRegistered(kTopicA)); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| FireInvalidation(kTopicA, V(1), "test"); |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| invalidation::Invalidation inv = FireInvalidation(kTopicA, V(3), "test"); |
| |
| // Check re-registration for topic B. Make sure the pending refresh from |
| // topic A gets cancelled. |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv)); |
| StorePolicy(kTopicB); |
| EXPECT_TRUE(IsInvalidatorRegistered(kTopicB)); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| |
| // Make sure future invalidations for topic B are processed. |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| inv = FireInvalidation(kTopicB, V(5), "test"); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv)); |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, UnregisterOnStoreLoaded) { |
| // Register for topic A. |
| StartInvalidator(); |
| StorePolicy(kTopicA); |
| EXPECT_TRUE(IsInvalidatorRegistered(kTopicA)); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| FireInvalidation(kTopicA, V(1), "test"); |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| |
| // Check unregistration when store is loaded with no invalidation topic id. |
| invalidation::Invalidation inv = FireInvalidation(kTopicA, V(2), "test"); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv)); |
| StorePolicy(kNoTopic); |
| EXPECT_FALSE(IsInvalidatorRegistered()); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| |
| // Check re-registration for topic B. |
| StorePolicy(kTopicB); |
| EXPECT_TRUE(IsInvalidatorRegistered(kTopicB)); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| FireInvalidation(kTopicB, V(5), "test"); |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, HandleInvalidation) { |
| // Register and fire invalidation |
| StorePolicy(kTopicA); |
| StartInvalidator(); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| const invalidation::Invalidation inv = |
| FireInvalidation(kTopicA, V(12), "test_payload"); |
| |
| // Make sure client info is set as soon as the invalidation is received. |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv)); |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| |
| // Make sure invalidation data is not removed from the client until the store |
| // is loaded. |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv)); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv)); |
| StorePolicy(kTopicA, V(12)); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| EXPECT_EQ(V(12), GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, HandleMultipleInvalidations) { |
| // Generate multiple invalidations. |
| StorePolicy(kTopicA); |
| StartInvalidator(); |
| const invalidation::Invalidation inv1 = |
| FireInvalidation(kTopicA, V(1), "test1"); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv1)); |
| const invalidation::Invalidation inv2 = |
| FireInvalidation(kTopicA, V(2), "test2"); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv2)); |
| const invalidation::Invalidation inv3 = |
| FireInvalidation(kTopicA, V(3), "test3"); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv3)); |
| |
| // Make sure the policy is refreshed once. |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| |
| // Make sure that the invalidation data is only removed from the client after |
| // the store is loaded with the latest version. |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| StorePolicy(kTopicA, V(1)); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv3)); |
| EXPECT_EQ(V(1), GetHighestHandledInvalidationVersion()); |
| StorePolicy(kTopicA, V(2)); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv3)); |
| EXPECT_EQ(V(2), GetHighestHandledInvalidationVersion()); |
| StorePolicy(kTopicA, V(3)); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| EXPECT_EQ(V(3), GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, |
| InitialHighestHandledInvalidationVersionNonZero) { |
| StorePolicy(kTopicA); |
| StartInvalidator(true, /* initialize */ |
| true, /* start_refresh_scheduler */ |
| V(2) /* 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::Invalidation inv1 = |
| FireInvalidation(kTopicA, V(1), "test1"); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| EXPECT_EQ(V(2), GetHighestHandledInvalidationVersion()); |
| |
| // Check that an invalidation whose version matches the highest handled so far |
| // is acknowledged but ignored otherwise. |
| const invalidation::Invalidation inv2 = |
| FireInvalidation(kTopicA, V(2), "test2"); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| EXPECT_EQ(V(2), GetHighestHandledInvalidationVersion()); |
| |
| // Check that an invalidation whose version is higher than the highest handled |
| // so far is handled, causing a policy refresh. |
| const invalidation::Invalidation inv3 = |
| FireInvalidation(kTopicA, V(3), "test3"); |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv3)); |
| StorePolicy(kTopicA, V(3)); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| EXPECT_EQ(V(3), GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, StoreLoadedBeforeRefresh) { |
| // Generate an invalidation. |
| StorePolicy(kTopicA); |
| StartInvalidator(); |
| const invalidation::Invalidation inv = |
| FireInvalidation(kTopicA, V(3), "test"); |
| |
| // Ensure that the policy is not refreshed and the invalidation is |
| // data is removed from the client if the store is loaded with the latest |
| // version before the refresh can occur. |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| StorePolicy(kTopicA, V(3)); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| EXPECT_EQ(V(3), GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, NoCallbackAfterShutdown) { |
| // Generate an invalidation. |
| StorePolicy(kTopicA); |
| StartInvalidator(); |
| invalidation::Invalidation inv = FireInvalidation(kTopicA, V(3), "test"); |
| |
| // Ensure that the policy refresh is not made after the invalidator is shut |
| // down. |
| ShutdownInvalidator(); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| DestroyInvalidator(); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, StateChanged) { |
| // Test invalidation service state changes while not registered. |
| StartInvalidator(); |
| DisableInvalidationService(); |
| EnableInvalidationService(); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| |
| // Test invalidation service state changes while registered. |
| StorePolicy(kTopicA); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| DisableInvalidationService(); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| DisableInvalidationService(); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| EnableInvalidationService(); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| EnableInvalidationService(); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| |
| // Test registration changes with invalidation service enabled. |
| StorePolicy(kNoTopic); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| StorePolicy(kNoTopic); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| StorePolicy(kTopicA); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| StorePolicy(kTopicA); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| |
| // Test registration changes with invalidation service disabled. |
| DisableInvalidationService(); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| StorePolicy(kNoTopic); |
| StorePolicy(kTopicA); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorTest, Disconnect) { |
| // Generate an invalidation. |
| StorePolicy(kTopicA); |
| StartInvalidator(); |
| const invalidation::Invalidation inv = |
| FireInvalidation(kTopicA, V(1), "test"); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| |
| // Ensure that the policy is not refreshed after disconnecting the core, but |
| // a call to indicate that invalidations are disabled is made. |
| DisconnectCore(); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| |
| // Ensure that invalidation service events do not cause refreshes while the |
| // invalidator is stopped. |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| DisableInvalidationService(); |
| EnableInvalidationService(); |
| |
| // Connect and disconnect without starting the refresh scheduler. |
| ConnectCore(); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| DisconnectCore(); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| |
| // Ensure that the invalidator returns to normal after reconnecting. |
| ConnectCore(); |
| StartRefreshScheduler(); |
| EXPECT_TRUE(CheckPolicyNotRefreshed()); |
| EXPECT_TRUE(InvalidationsEnabled()); |
| const invalidation::Invalidation inv5 = |
| FireInvalidation(kTopicA, V(5), "test"); |
| EXPECT_TRUE(ClientInvalidationInfoMatches(inv5)); |
| EXPECT_TRUE(CheckPolicyRefreshed()); |
| DisableInvalidationService(); |
| EXPECT_FALSE(InvalidationsEnabled()); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| class CloudPolicyInvalidatorOwnerNameTest |
| : public CloudPolicyInvalidatorTestBase { |
| protected: |
| PolicyInvalidationScope GetPolicyInvalidationScope() const override { |
| return scope_; |
| } |
| |
| PolicyInvalidationScope scope_; |
| }; |
| |
| TEST_F(CloudPolicyInvalidatorOwnerNameTest, GetOwnerNameForUserScope) { |
| scope_ = PolicyInvalidationScope::kUser; |
| StartInvalidator(false, /* initialize */ |
| false, /* start_refresh_scheduler */ |
| 0 /* highest_handled_invalidation_version*/); |
| ASSERT_TRUE(invalidator()); |
| EXPECT_EQ("CloudPolicy.User", invalidator()->GetOwnerName()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorOwnerNameTest, GetOwnerNameForDeviceScope) { |
| scope_ = PolicyInvalidationScope::kDevice; |
| StartInvalidator(false, /* initialize */ |
| false, /* start_refresh_scheduler */ |
| 0 /* highest_handled_invalidation_version*/); |
| ASSERT_TRUE(invalidator()); |
| EXPECT_EQ("CloudPolicy.Device", invalidator()->GetOwnerName()); |
| } |
| |
| TEST_F(CloudPolicyInvalidatorOwnerNameTest, |
| GetOwnerNameForDeviceLocalAccountScope) { |
| scope_ = PolicyInvalidationScope::kDeviceLocalAccount; |
| StartInvalidator(false, /* initialize */ |
| false, /* start_refresh_scheduler */ |
| 0 /* highest_handled_invalidation_version*/); |
| ASSERT_TRUE(invalidator()); |
| EXPECT_EQ("CloudPolicy.DeviceLocalAccount.test_account", |
| invalidator()->GetOwnerName()); |
| } |
| |
| class CloudPolicyInvalidatorUserTypedTest |
| : public CloudPolicyInvalidatorTestBase, |
| public testing::WithParamInterface<PolicyInvalidationScope> { |
| public: |
| CloudPolicyInvalidatorUserTypedTest( |
| const CloudPolicyInvalidatorUserTypedTest&) = delete; |
| CloudPolicyInvalidatorUserTypedTest& operator=( |
| const CloudPolicyInvalidatorUserTypedTest&) = delete; |
| |
| protected: |
| CloudPolicyInvalidatorUserTypedTest() = default; |
| |
| base::HistogramBase::Count GetCount(MetricPolicyRefresh metric); |
| base::HistogramBase::Count GetInvalidationCount(PolicyInvalidationType type); |
| |
| private: |
| // CloudPolicyInvalidatorTest: |
| PolicyInvalidationScope GetPolicyInvalidationScope() const override; |
| |
| base::HistogramTester histogram_tester_; |
| }; |
| |
| base::HistogramBase::Count CloudPolicyInvalidatorUserTypedTest::GetCount( |
| MetricPolicyRefresh metric) { |
| const char* metric_name = CloudPolicyInvalidator::GetPolicyRefreshMetricName( |
| GetPolicyInvalidationScope()); |
| return histogram_tester_.GetHistogramSamplesSinceCreation(metric_name) |
| ->GetCount(metric); |
| } |
| |
| base::HistogramBase::Count |
| 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, RefreshMetricsUnregistered) { |
| // Store loads occurring before invalidation registration are not counted. |
| StartInvalidator(); |
| StorePolicy(kNoTopic, 0, false /* policy_changed */); |
| StorePolicy(kNoTopic, 0, true /* policy_changed */); |
| EXPECT_EQ(0, GetCount(METRIC_POLICY_REFRESH_CHANGED)); |
| EXPECT_EQ(0, 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, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_P(CloudPolicyInvalidatorUserTypedTest, RefreshMetricsNoInvalidations) { |
| // Store loads occurring while registered should be differentiated depending |
| // on whether the invalidation service was enabled or not. |
| StorePolicy(kTopicA); |
| StartInvalidator(); |
| |
| // Initially, invalidations have not been enabled past the grace period, so |
| // invalidations are OFF. |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS)); |
| |
| // If the clock advances less than the grace period, invalidations are OFF. |
| AdvanceClock(base::Seconds(1)); |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| EXPECT_EQ(2, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS)); |
| |
| // After the grace period elapses, invalidations are ON. |
| AdvanceClock(base::Seconds(CloudPolicyInvalidator::kInvalidationGracePeriod)); |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| EXPECT_EQ(1, GetCount(METRIC_POLICY_REFRESH_CHANGED)); |
| |
| // After the invalidation service is disabled, invalidations are OFF. |
| DisableInvalidationService(); |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| EXPECT_EQ(3, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS)); |
| |
| // Enabling the invalidation service results in a new grace period, so |
| // invalidations are OFF. |
| EnableInvalidationService(); |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| EXPECT_EQ(4, GetCount(METRIC_POLICY_REFRESH_CHANGED_NO_INVALIDATIONS)); |
| |
| // After the grace period elapses, invalidations are ON. |
| AdvanceClock(base::Seconds(CloudPolicyInvalidator::kInvalidationGracePeriod)); |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| |
| 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, GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_P(CloudPolicyInvalidatorUserTypedTest, RefreshMetricsInvalidation) { |
| // Store loads after an invalidation are not counted as invalidated. |
| StartInvalidator(); |
| StorePolicy(kTopicA); |
| AdvanceClock(base::Seconds(CloudPolicyInvalidator::kInvalidationGracePeriod)); |
| FireInvalidation(kTopicA, V(5), "test"); |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| StorePolicy(kTopicA, V(5), true /* policy_changed */); |
| EXPECT_EQ(V(5), GetHighestHandledInvalidationVersion()); |
| |
| // Store loads after the invalidation is complete are not counted as |
| // invalidated. |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| StorePolicy(kTopicA, 0, true /* policy_changed */); |
| StorePolicy(kTopicA, 0, false /* policy_changed */); |
| |
| EXPECT_EQ(4, GetCount(METRIC_POLICY_REFRESH_CHANGED)); |
| EXPECT_EQ(0, 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(5), GetHighestHandledInvalidationVersion()); |
| } |
| |
| TEST_P(CloudPolicyInvalidatorUserTypedTest, ExpiredInvalidations) { |
| StorePolicy(kTopicA, 0, false, Now()); |
| StartInvalidator(); |
| |
| // Invalidations fired before the last fetch time (adjusted by max time delta) |
| // should be ignored (and count as expired). |
| base::Time time = Now() - (invalidation_timeouts::kMaxInvalidationTimeDelta + |
| base::Seconds(300)); |
| invalidation::Invalidation inv = |
| FireInvalidation(kTopicA, GetVersion(time), "test"); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| ASSERT_TRUE(CheckPolicyNotRefreshed()); |
| |
| inv = FireInvalidation(kTopicA, GetVersion(time), ""); // no payload |
| ASSERT_TRUE(CheckPolicyNotRefreshed()); |
| |
| time += base::Minutes(5) - base::Seconds(1); |
| inv = FireInvalidation(kTopicA, GetVersion(time), "test"); |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| ASSERT_TRUE(CheckPolicyNotRefreshed()); |
| |
| // Invalidations fired after the last fetch should not be ignored. |
| time += base::Seconds(1); |
| inv = FireInvalidation(kTopicA, GetVersion(time), ""); // no payload |
| EXPECT_TRUE(ClientInvalidationInfoIsUnset()); |
| ASSERT_TRUE(CheckPolicyRefreshed( |
| base::Minutes(CloudPolicyInvalidator::kMissingPayloadDelay))); |
| |
| time += base::Minutes(10); |
| inv = FireInvalidation(kTopicA, GetVersion(time), "test"); |
| ASSERT_TRUE(ClientInvalidationInfoMatches(inv)); |
| ASSERT_TRUE(CheckPolicyRefreshed()); |
| |
| time += base::Minutes(10); |
| inv = FireInvalidation(kTopicA, GetVersion(time), "test"); |
| ASSERT_TRUE(ClientInvalidationInfoMatches(inv)); |
| ASSERT_TRUE(CheckPolicyRefreshed()); |
| |
| time += base::Minutes(10); |
| inv = FireInvalidation(kTopicA, GetVersion(time), "test"); |
| ASSERT_TRUE(ClientInvalidationInfoMatches(inv)); |
| ASSERT_TRUE(CheckPolicyRefreshed()); |
| |
| // 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(2, GetInvalidationCount(POLICY_INVALIDATION_TYPE_EXPIRED)); |
| |
| EXPECT_EQ(0, GetHighestHandledInvalidationVersion()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| CloudPolicyInvalidatorUserTypedTestInstance, |
| CloudPolicyInvalidatorUserTypedTest, |
| testing::Values(PolicyInvalidationScope::kUser, |
| PolicyInvalidationScope::kDevice, |
| PolicyInvalidationScope::kDeviceLocalAccount)); |
| |
| } // namespace policy |