| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/policy/app_install_event_logger.h" |
| |
| #include <stdint.h> |
| |
| #include "base/json/json_writer.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "chrome/browser/prefs/browser_prefs.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chromeos/dbus/cros_disks_client.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/fake_power_manager_client.h" |
| #include "chromeos/disks/disk_mount_manager.h" |
| #include "chromeos/disks/mock_disk_mount_manager.h" |
| #include "chromeos/network/network_handler.h" |
| #include "components/arc/arc_prefs.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/policy/proto/device_management_backend.pb.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::Mock; |
| using testing::_; |
| |
| namespace em = enterprise_management; |
| |
| namespace policy { |
| |
| namespace { |
| |
| constexpr char kStatefulMountPath[] = "/mnt/stateful_partition"; |
| constexpr char kPackageName[] = "com.example.app"; |
| constexpr char kPackageName2[] = "com.example.app2"; |
| constexpr char kPackageName3[] = "com.example.app3"; |
| constexpr char kPackageName4[] = "com.example.app4"; |
| constexpr char kPackageName5[] = "com.example.app5"; |
| const int kTimestamp = 123456; |
| |
| MATCHER_P(MatchProto, expected, "matches protobuf") { |
| return arg.SerializePartialAsString() == expected.SerializePartialAsString(); |
| } |
| |
| MATCHER_P(MatchEventExceptTimestamp, expected, "event matches") { |
| em::AppInstallReportLogEvent actual_event; |
| actual_event.MergeFrom(arg); |
| actual_event.clear_timestamp(); |
| |
| em::AppInstallReportLogEvent expected_event; |
| expected_event.MergeFrom(expected); |
| expected_event.clear_timestamp(); |
| |
| return actual_event.SerializePartialAsString() == |
| expected_event.SerializePartialAsString(); |
| } |
| |
| ACTION_TEMPLATE(SaveTimestamp, |
| HAS_1_TEMPLATE_PARAMS(int, k), |
| AND_1_VALUE_PARAMS(out)) { |
| *out = testing::get<k>(args).timestamp(); |
| }; |
| |
| int64_t GetCurrentTimestamp() { |
| return (base::Time::Now() - base::Time::UnixEpoch()).InMicroseconds(); |
| } |
| |
| class MockAppInstallEventLoggerDelegate |
| : public AppInstallEventLogger::Delegate { |
| public: |
| MockAppInstallEventLoggerDelegate() = default; |
| |
| MOCK_METHOD2(Add, |
| void(const std::set<std::string>& packages, |
| const em::AppInstallReportLogEvent& event)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockAppInstallEventLoggerDelegate); |
| }; |
| |
| void SetPolicy(policy::PolicyMap* map, |
| const char* name, |
| std::unique_ptr<base::Value> value) { |
| map->Set(name, policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER, |
| policy::POLICY_SOURCE_CLOUD, std::move(value), nullptr); |
| } |
| |
| } // namespace |
| |
| class AppInstallEventLoggerTest : public testing::Test { |
| protected: |
| AppInstallEventLoggerTest() |
| : browser_thread_bundle_( |
| base::test::ScopedTaskEnvironment::MainThreadType::UI, |
| base::test::ScopedTaskEnvironment::ExecutionMode::QUEUED) {} |
| |
| void SetUp() override { |
| RegisterLocalState(pref_service_.registry()); |
| TestingBrowserProcess::GetGlobal()->SetLocalState(&pref_service_); |
| |
| chromeos::DBusThreadManager::GetSetterForTesting()->SetPowerManagerClient( |
| std::make_unique<chromeos::FakePowerManagerClient>()); |
| chromeos::DBusThreadManager::Initialize(); |
| chromeos::NetworkHandler::Initialize(); |
| |
| disk_mount_manager_ = new chromeos::disks::MockDiskMountManager; |
| chromeos::disks::DiskMountManager::InitializeForTesting( |
| disk_mount_manager_); |
| disk_mount_manager_->CreateDiskEntryForMountDevice( |
| chromeos::disks::DiskMountManager::MountPointInfo( |
| "/dummy/device/usb", kStatefulMountPath, |
| chromeos::MOUNT_TYPE_DEVICE, chromeos::disks::MOUNT_CONDITION_NONE), |
| "device_id", "device_label", "vendor", "product", |
| chromeos::DEVICE_TYPE_UNKNOWN, 1 << 20 /* total_size_in_bytes */, |
| false /* is_parent */, false /* has_media */, true /* on_boot_device */, |
| true /* on_removable_device */, "ext4"); |
| } |
| |
| void TearDown() override { |
| logger_.reset(); |
| browser_thread_bundle_.RunUntilIdle(); |
| chromeos::NetworkHandler::Shutdown(); |
| chromeos::DBusThreadManager::Shutdown(); |
| chromeos::disks::DiskMountManager::Shutdown(); |
| TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr); |
| } |
| |
| // Runs |function|, verifies that the expected event is added to the logs for |
| // all apps in |packages| and its timestamp is set to the time at which the |
| // |function| is run. |
| template <typename T> |
| void RunAndVerifyAdd(T function, const std::set<std::string>& packages) { |
| Mock::VerifyAndClearExpectations(&delegate_); |
| |
| int64_t timestamp = 0; |
| EXPECT_CALL(delegate_, Add(packages, MatchEventExceptTimestamp(event_))) |
| .WillOnce(SaveTimestamp<1>(×tamp)); |
| const int64_t before = GetCurrentTimestamp(); |
| function(); |
| const int64_t after = GetCurrentTimestamp(); |
| Mock::VerifyAndClearExpectations(&delegate_); |
| |
| EXPECT_LE(before, timestamp); |
| EXPECT_GE(after, timestamp); |
| } |
| |
| void CreateLogger() { |
| event_.set_event_type(em::AppInstallReportLogEvent::CANCELED); |
| RunAndVerifyAdd( |
| [&]() { |
| logger_ = |
| std::make_unique<AppInstallEventLogger>(&delegate_, &profile_); |
| }, |
| {}); |
| event_.set_event_type(em::AppInstallReportLogEvent::SUCCESS); |
| } |
| |
| content::TestBrowserThreadBundle browser_thread_bundle_; |
| TestingProfile profile_; |
| TestingPrefServiceSimple pref_service_; |
| |
| // Owned by chromeos::disks::DiskMountManager. |
| chromeos::disks::MockDiskMountManager* disk_mount_manager_ = nullptr; |
| |
| MockAppInstallEventLoggerDelegate delegate_; |
| |
| em::AppInstallReportLogEvent event_; |
| |
| std::unique_ptr<AppInstallEventLogger> logger_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AppInstallEventLoggerTest); |
| }; |
| |
| // Store lists of apps for which push-install has been requested and is still |
| // pending. Clear all data related to app-install event log collection. Verify |
| // that the lists are cleared. |
| TEST_F(AppInstallEventLoggerTest, Clear) { |
| base::ListValue list; |
| list.AppendString("test"); |
| profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsRequested, list); |
| profile_.GetPrefs()->Set(arc::prefs::kArcPushInstallAppsPending, list); |
| AppInstallEventLogger::Clear(&profile_); |
| EXPECT_TRUE(profile_.GetPrefs() |
| ->FindPreference(arc::prefs::kArcPushInstallAppsRequested) |
| ->IsDefaultValue()); |
| EXPECT_TRUE(profile_.GetPrefs() |
| ->FindPreference(arc::prefs::kArcPushInstallAppsPending) |
| ->IsDefaultValue()); |
| } |
| |
| // Adds an event with a timestamp. Verifies that the event is added to the log |
| // and the timestamp is not changed. |
| TEST_F(AppInstallEventLoggerTest, Add) { |
| CreateLogger(); |
| |
| event_.set_timestamp(kTimestamp); |
| std::unique_ptr<em::AppInstallReportLogEvent> event = |
| std::make_unique<em::AppInstallReportLogEvent>(); |
| event->MergeFrom(event_); |
| |
| EXPECT_CALL(delegate_, |
| Add(std::set<std::string>{kPackageName}, MatchProto(event_))); |
| logger_->Add(kPackageName, false /* gather_disk_space_info */, |
| std::move(event)); |
| } |
| |
| // Adds an event without a timestamp. Verifies that the event is added to the |
| // log and the timestamp is set to the current time. |
| TEST_F(AppInstallEventLoggerTest, AddSetsTimestamp) { |
| CreateLogger(); |
| |
| std::unique_ptr<em::AppInstallReportLogEvent> event = |
| std::make_unique<em::AppInstallReportLogEvent>(); |
| event->MergeFrom(event_); |
| |
| RunAndVerifyAdd( |
| [&]() { |
| logger_->Add(kPackageName, false /* gather_disk_space_info */, |
| std::move(event)); |
| }, |
| {kPackageName}); |
| } |
| |
| // Adds an event with a timestamp, requesting that disk space information be |
| // added to it. Verifies that a background task is posted that consults the disk |
| // mount manager. Then, verifies that after the background task has run, the |
| // event is added. |
| // |
| // It is not possible to test that disk size information is retrieved correctly |
| // as a mounted stateful partition cannot be simulated in unit tests. |
| TEST_F(AppInstallEventLoggerTest, AddSetsDiskSpaceInfo) { |
| CreateLogger(); |
| |
| event_.set_timestamp(kTimestamp); |
| std::unique_ptr<em::AppInstallReportLogEvent> event = |
| std::make_unique<em::AppInstallReportLogEvent>(); |
| event->MergeFrom(event_); |
| |
| EXPECT_CALL(*disk_mount_manager_, disks()).Times(0); |
| EXPECT_CALL(delegate_, Add(_, _)).Times(0); |
| logger_->Add(kPackageName, true /* gather_disk_space_info */, |
| std::move(event)); |
| Mock::VerifyAndClearExpectations(disk_mount_manager_); |
| Mock::VerifyAndClearExpectations(&delegate_); |
| |
| EXPECT_CALL(*disk_mount_manager_, disks()); |
| EXPECT_CALL(delegate_, |
| Add(std::set<std::string>{kPackageName}, MatchProto(event_))); |
| browser_thread_bundle_.RunUntilIdle(); |
| } |
| |
| // Adds an event without a timestamp, requesting that disk space information be |
| // added to it. Verifies that a background task is posted that consults the disk |
| // mount manager. Then, verifies that after the background task has run, the |
| // event is added and its timestamp is set to the current time before posting |
| // the background task. |
| // |
| // It is not possible to test that disk size information is retrieved correctly |
| // as a mounted stateful partition cannot be simulated in unit tests. |
| TEST_F(AppInstallEventLoggerTest, AddSetsTimestampAndDiskSpaceInfo) { |
| CreateLogger(); |
| |
| std::unique_ptr<em::AppInstallReportLogEvent> event = |
| std::make_unique<em::AppInstallReportLogEvent>(); |
| event->MergeFrom(event_); |
| |
| EXPECT_CALL(*disk_mount_manager_, disks()).Times(0); |
| EXPECT_CALL(delegate_, Add(_, _)).Times(0); |
| const int64_t before = GetCurrentTimestamp(); |
| logger_->Add(kPackageName, true /* gather_disk_space_info */, |
| std::move(event)); |
| const int64_t after = GetCurrentTimestamp(); |
| Mock::VerifyAndClearExpectations(disk_mount_manager_); |
| Mock::VerifyAndClearExpectations(&delegate_); |
| |
| int64_t timestamp = 0; |
| EXPECT_CALL(*disk_mount_manager_, disks()); |
| EXPECT_CALL(delegate_, Add(std::set<std::string>{kPackageName}, |
| MatchEventExceptTimestamp(event_))) |
| .WillOnce(SaveTimestamp<1>(×tamp)); |
| browser_thread_bundle_.RunUntilIdle(); |
| |
| EXPECT_LE(before, timestamp); |
| EXPECT_GE(after, timestamp); |
| } |
| |
| TEST_F(AppInstallEventLoggerTest, UpdatePolicy) { |
| CreateLogger(); |
| |
| policy::PolicyMap new_policy_map; |
| |
| base::DictionaryValue arc_policy; |
| auto list = std::make_unique<base::ListValue>(); |
| |
| // Test that REQUIRED, PREINSTALLED and FORCE_INSTALLED are markers to include |
| // app to the tracking. BLOCKED and AVAILABLE are excluded. |
| auto package1 = std::make_unique<base::DictionaryValue>(); |
| package1->SetString("installType", "REQUIRED"); |
| package1->SetString("packageName", kPackageName); |
| list->Append(std::move(package1)); |
| auto package2 = std::make_unique<base::DictionaryValue>(); |
| package2->SetString("installType", "PREINSTALLED"); |
| package2->SetString("packageName", kPackageName2); |
| list->Append(std::move(package2)); |
| auto package3 = std::make_unique<base::DictionaryValue>(); |
| package3->SetString("installType", "FORCE_INSTALLED"); |
| package3->SetString("packageName", kPackageName3); |
| list->Append(std::move(package3)); |
| auto package4 = std::make_unique<base::DictionaryValue>(); |
| package4->SetString("installType", "BLOCKED"); |
| package4->SetString("packageName", kPackageName4); |
| list->Append(std::move(package4)); |
| auto package5 = std::make_unique<base::DictionaryValue>(); |
| package5->SetString("installType", "AVAILABLE"); |
| package5->SetString("packageName", kPackageName5); |
| list->Append(std::move(package5)); |
| arc_policy.SetList("applications", std::move(list)); |
| |
| std::string arc_policy_string; |
| base::JSONWriter::Write(arc_policy, &arc_policy_string); |
| SetPolicy(&new_policy_map, key::kArcEnabled, |
| std::make_unique<base::Value>(true)); |
| SetPolicy(&new_policy_map, key::kArcPolicy, |
| std::make_unique<base::Value>(arc_policy_string)); |
| |
| // Expected CANCELED with empty package set |
| event_.set_event_type(em::AppInstallReportLogEvent::CANCELED); |
| EXPECT_CALL(delegate_, |
| Add(std::set<std::string>(), MatchEventExceptTimestamp(event_))); |
| |
| logger_->OnPolicyUpdated(policy::PolicyNamespace(), |
| policy::PolicyMap() /* previous */, new_policy_map); |
| Mock::VerifyAndClearExpectations(&delegate_); |
| |
| // Expected new packages added with disk info. |
| event_.set_event_type(em::AppInstallReportLogEvent::SERVER_REQUEST); |
| EXPECT_CALL(delegate_, Add(std::set<std::string>{kPackageName, kPackageName3}, |
| MatchEventExceptTimestamp(event_))); |
| EXPECT_CALL(*disk_mount_manager_, disks()); |
| browser_thread_bundle_.RunUntilIdle(); |
| Mock::VerifyAndClearExpectations(&delegate_); |
| |
| // To avoid extra logging. |
| g_browser_process->local_state()->SetBoolean(prefs::kWasRestarted, true); |
| } |
| |
| } // namespace policy |