chromium/chrome/browser/ash/policy/reporting/arc_app_install_event_log_collector_unittest.cc

// Copyright 2018 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/ash/policy/reporting/arc_app_install_event_log_collector.h"

#include <vector>

#include "ash/components/arc/mojom/app.mojom.h"
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "chrome/browser/ash/app_list/arc/arc_app_test.h"
#include "chrome/browser/browser_process.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/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/dbus/power/fake_power_manager_client.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/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace em = enterprise_management;

namespace policy {

namespace {

constexpr char kEthernetServicePath[] = "/service/eth1";
constexpr char kWifiServicePath[] = "/service/wifi1";

constexpr char kPackageName[] = "com.example.app";
constexpr char kPackageName2[] = "com.example.app2";

class FakeAppInstallEventLogCollectorDelegate
    : public ArcAppInstallEventLogCollector::Delegate {
 public:
  FakeAppInstallEventLogCollectorDelegate() = default;

  FakeAppInstallEventLogCollectorDelegate(
      const FakeAppInstallEventLogCollectorDelegate&) = delete;
  FakeAppInstallEventLogCollectorDelegate& operator=(
      const FakeAppInstallEventLogCollectorDelegate&) = delete;

  ~FakeAppInstallEventLogCollectorDelegate() override = default;

  struct Request {
    Request(bool for_all,
            bool add_disk_space_info,
            const std::string& package_name,
            const em::AppInstallReportLogEvent& event)
        : for_all(for_all),
          add_disk_space_info(add_disk_space_info),
          package_name(package_name),
          event(event) {}
    const bool for_all;
    const bool add_disk_space_info;
    const std::string package_name;
    const em::AppInstallReportLogEvent event;
  };

  // ArcAppInstallEventLogCollector::Delegate:
  void AddForAllPackages(
      std::unique_ptr<em::AppInstallReportLogEvent> event) override {
    ++add_for_all_count_;
    requests_.emplace_back(true /* for_all */, false /* add_disk_space_info */,
                           std::string() /* package_name */, *event);
  }

  void Add(const std::string& package_name,
           bool add_disk_space_info,
           std::unique_ptr<em::AppInstallReportLogEvent> event) override {
    ++add_count_;
    requests_.emplace_back(false /* for_all */, add_disk_space_info,
                           package_name, *event);
  }

  void UpdatePolicySuccessRate(const std::string& package_name,
                               bool success) override {
    ++update_policy_success_rate_count_;
    auto event = std::make_unique<em::AppInstallReportLogEvent>();
    event->set_event_type(
        success ? em::AppInstallReportLogEvent::INSTALLATION_FINISHED
                : em::AppInstallReportLogEvent::INSTALLATION_FAILED);

    requests_.emplace_back(false /* for_all */, false /* add_disk_space_info */,
                           package_name, *event);
  }

  int add_for_all_count() const { return add_for_all_count_; }

  int add_count() const { return add_count_; }

  int update_policy_success_rate_count() const {
    return update_policy_success_rate_count_;
  }

  const em::AppInstallReportLogEvent& last_event() const {
    return last_request().event;
  }
  const Request& last_request() const { return requests_.back(); }

  const em::AppInstallReportLogEvent& event_at(int index) const {
    return request_at(index).event;
  }

  const Request& request_at(int index) const { return requests_.at(index); }

  const std::vector<Request>& requests() const { return requests_; }

 private:
  int add_for_all_count_ = 0;
  int add_count_ = 0;
  int update_policy_success_rate_count_ = 0;
  std::vector<Request> requests_;
};

int64_t TimeToTimestamp(base::Time time) {
  return (time - base::Time::UnixEpoch()).InMicroseconds();
}

}  // namespace

class ArcAppInstallEventLogCollectorTest : public testing::Test {
 public:
  ArcAppInstallEventLogCollectorTest(
      const ArcAppInstallEventLogCollectorTest&) = delete;
  ArcAppInstallEventLogCollectorTest& operator=(
      const ArcAppInstallEventLogCollectorTest&) = delete;

 protected:
  ArcAppInstallEventLogCollectorTest() = default;
  ~ArcAppInstallEventLogCollectorTest() override = default;

  void SetUp() override {
    RegisterLocalState(pref_service_.registry());
    TestingBrowserProcess::GetGlobal()->SetLocalState(&pref_service_);

    chromeos::PowerManagerClient::InitializeFake();
    profile_ = std::make_unique<TestingProfile>();
    arc_app_test_.SetUp(profile_.get());

    network_handler_test_helper_ =
        std::make_unique<ash::NetworkHandlerTestHelper>();
    network_handler_test_helper_->service_test()->AddService(
        kEthernetServicePath, "eth1_guid", "eth1", shill::kTypeEthernet,
        shill::kStateIdle, true /* visible */);
    network_handler_test_helper_->service_test()->AddService(
        kWifiServicePath, "wifi1_guid", "wifi1", shill::kTypeEthernet,
        shill::kStateIdle, true /* visible */);
    base::RunLoop().RunUntilIdle();
  }

  void TearDown() override {
    network_handler_test_helper_.reset();
    arc_app_test_.TearDown();

    profile_.reset();
    chromeos::PowerManagerClient::Shutdown();
    TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
  }

  void SetNetworkState(
      network::NetworkConnectionTracker::NetworkConnectionObserver* observer,
      const std::string& service_path,
      const std::string& state) {
    network_handler_test_helper_->service_test()->SetServiceProperty(
        service_path, shill::kStateProperty, base::Value(state));
    base::RunLoop().RunUntilIdle();

    network::mojom::ConnectionType connection_type =
        network::mojom::ConnectionType::CONNECTION_NONE;
    const std::string* network_state =
        network_handler_test_helper_->service_test()
            ->GetServiceProperties(kWifiServicePath)
            ->FindString(shill::kStateProperty);
    if (network_state && *network_state == shill::kStateOnline) {
      connection_type = network::mojom::ConnectionType::CONNECTION_WIFI;
    }
    network_state = network_handler_test_helper_->service_test()
                        ->GetServiceProperties(kEthernetServicePath)
                        ->FindString(shill::kStateProperty);
    if (network_state && *network_state == shill::kStateOnline) {
      connection_type = network::mojom::ConnectionType::CONNECTION_ETHERNET;
    }
    if (observer)
      observer->OnConnectionChanged(connection_type);
    base::RunLoop().RunUntilIdle();
  }

  TestingProfile* profile() { return profile_.get(); }
  FakeAppInstallEventLogCollectorDelegate* delegate() { return &delegate_; }
  ArcAppListPrefs* app_prefs() { return arc_app_test_.arc_app_list_prefs(); }

  const std::set<std::string> packages_ = {kPackageName};

 private:
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<ash::NetworkHandlerTestHelper> network_handler_test_helper_;
  std::unique_ptr<TestingProfile> profile_;
  FakeAppInstallEventLogCollectorDelegate delegate_;
  TestingPrefServiceSimple pref_service_;
  ArcAppTest arc_app_test_;
};

// Test the case when collector is created and destroyed inside the one user
// session. In this case no event is generated. This happens for example when
// all apps are installed in context of the same user session.
TEST_F(ArcAppInstallEventLogCollectorTest, NoEventsByDefault) {
  std::unique_ptr<ArcAppInstallEventLogCollector> collector =
      std::make_unique<ArcAppInstallEventLogCollector>(delegate(), profile(),
                                                       packages_);
  collector.reset();

  EXPECT_EQ(0, delegate()->add_count());
  EXPECT_EQ(0, delegate()->add_for_all_count());
  EXPECT_EQ(0, delegate()->update_policy_success_rate_count());
}

TEST_F(ArcAppInstallEventLogCollectorTest, LoginLogout) {
  std::unique_ptr<ArcAppInstallEventLogCollector> collector =
      std::make_unique<ArcAppInstallEventLogCollector>(delegate(), profile(),
                                                       packages_);

  EXPECT_EQ(0, delegate()->add_for_all_count());

  collector->OnLogin();
  EXPECT_EQ(1, delegate()->add_for_all_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
            delegate()->last_event().event_type());
  EXPECT_EQ(em::AppInstallReportLogEvent::LOGIN,
            delegate()->last_event().session_state_change_type());
  EXPECT_TRUE(delegate()->last_event().has_online());
  EXPECT_FALSE(delegate()->last_event().online());

  collector->OnLogout();
  EXPECT_EQ(2, delegate()->add_for_all_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
            delegate()->last_event().event_type());
  EXPECT_EQ(em::AppInstallReportLogEvent::LOGOUT,
            delegate()->last_event().session_state_change_type());
  EXPECT_FALSE(delegate()->last_event().has_online());

  collector.reset();

  EXPECT_EQ(2, delegate()->add_for_all_count());
  EXPECT_EQ(0, delegate()->add_count());
}

TEST_F(ArcAppInstallEventLogCollectorTest, LoginTypes) {
  {
    ArcAppInstallEventLogCollector collector(delegate(), profile(), packages_);
    collector.OnLogin();
    EXPECT_EQ(1, delegate()->add_for_all_count());
    EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
              delegate()->last_event().event_type());
    EXPECT_EQ(em::AppInstallReportLogEvent::LOGIN,
              delegate()->last_event().session_state_change_type());
    EXPECT_TRUE(delegate()->last_event().has_online());
    EXPECT_FALSE(delegate()->last_event().online());
  }

  {
    // Check login after restart. No log is expected.
    ArcAppInstallEventLogCollector collector(delegate(), profile(), packages_);
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        ash::switches::kLoginUser);
    collector.OnLogin();
    EXPECT_EQ(1, delegate()->add_for_all_count());
  }

  {
    // Check logout on restart. No log is expected.
    ArcAppInstallEventLogCollector collector(delegate(), profile(), packages_);
    g_browser_process->local_state()->SetBoolean(prefs::kWasRestarted, true);
    collector.OnLogout();
    EXPECT_EQ(1, delegate()->add_for_all_count());
  }

  EXPECT_EQ(0, delegate()->add_count());
}

TEST_F(ArcAppInstallEventLogCollectorTest, SuspendResume) {
  std::unique_ptr<ArcAppInstallEventLogCollector> collector =
      std::make_unique<ArcAppInstallEventLogCollector>(delegate(), profile(),
                                                       packages_);

  chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
      power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(1, delegate()->add_for_all_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
            delegate()->last_event().event_type());
  EXPECT_EQ(em::AppInstallReportLogEvent::SUSPEND,
            delegate()->last_event().session_state_change_type());

  chromeos::FakePowerManagerClient::Get()->SendSuspendDone();
  EXPECT_EQ(2, delegate()->add_for_all_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
            delegate()->last_event().event_type());
  EXPECT_EQ(em::AppInstallReportLogEvent::RESUME,
            delegate()->last_event().session_state_change_type());

  collector.reset();

  EXPECT_EQ(0, delegate()->add_count());
}

// Connect to Ethernet. Start log collector. Verify that a login event with
// network state online is recorded. Then, connect to WiFi and disconnect from
// Ethernet, in this order. Verify that no event is recorded. Then, disconnect
// from WiFi. Verify that a connectivity change event is recorded. Then, connect
// to WiFi with a pending captive portal. Verify that no event is recorded.
// Then, pass the captive portal. Verify that a connectivity change is recorded.
TEST_F(ArcAppInstallEventLogCollectorTest, ConnectivityChanges) {
  SetNetworkState(nullptr, kEthernetServicePath, shill::kStateOnline);

  std::unique_ptr<ArcAppInstallEventLogCollector> collector =
      std::make_unique<ArcAppInstallEventLogCollector>(delegate(), profile(),
                                                       packages_);

  EXPECT_EQ(0, delegate()->add_for_all_count());

  collector->OnLogin();
  EXPECT_EQ(1, delegate()->add_for_all_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::SESSION_STATE_CHANGE,
            delegate()->last_event().event_type());
  EXPECT_EQ(em::AppInstallReportLogEvent::LOGIN,
            delegate()->last_event().session_state_change_type());
  EXPECT_TRUE(delegate()->last_event().online());

  SetNetworkState(collector.get(), kWifiServicePath, shill::kStateOnline);
  EXPECT_EQ(1, delegate()->add_for_all_count());

  SetNetworkState(collector.get(), kEthernetServicePath, shill::kStateIdle);
  EXPECT_EQ(1, delegate()->add_for_all_count());

  SetNetworkState(collector.get(), kWifiServicePath, shill::kStateIdle);
  EXPECT_EQ(2, delegate()->add_for_all_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::CONNECTIVITY_CHANGE,
            delegate()->last_event().event_type());
  EXPECT_FALSE(delegate()->last_event().online());

  SetNetworkState(collector.get(), kWifiServicePath,
                  shill::kStateNoConnectivity);
  EXPECT_EQ(2, delegate()->add_for_all_count());

  SetNetworkState(collector.get(), kWifiServicePath, shill::kStateOnline);
  EXPECT_EQ(3, delegate()->add_for_all_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::CONNECTIVITY_CHANGE,
            delegate()->last_event().event_type());
  EXPECT_TRUE(delegate()->last_event().online());

  collector.reset();

  EXPECT_EQ(3, delegate()->add_for_all_count());
  EXPECT_EQ(0, delegate()->add_count());
}

TEST_F(ArcAppInstallEventLogCollectorTest, InstallPackages) {
  arc::mojom::AppHost* const app_host = app_prefs();

  std::unique_ptr<ArcAppInstallEventLogCollector> collector =
      std::make_unique<ArcAppInstallEventLogCollector>(delegate(), profile(),
                                                       packages_);

  app_host->OnInstallationStarted(kPackageName);
  ASSERT_EQ(1, delegate()->add_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_STARTED,
            delegate()->last_event().event_type());
  EXPECT_EQ(kPackageName, delegate()->last_request().package_name);
  EXPECT_TRUE(delegate()->last_request().add_disk_space_info);

  // kPackageName2 is not in the pending set.
  app_host->OnInstallationStarted(kPackageName2);
  EXPECT_EQ(1, delegate()->add_count());

  arc::mojom::InstallationResult result;
  result.package_name = kPackageName;
  result.success = true;
  app_host->OnInstallationFinished(
      arc::mojom::InstallationResultPtr(result.Clone()));
  EXPECT_EQ(2, delegate()->add_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_FINISHED,
            delegate()->last_event().event_type());
  EXPECT_EQ(kPackageName, delegate()->last_request().package_name);
  EXPECT_TRUE(delegate()->last_request().add_disk_space_info);

  collector->OnPendingPackagesChanged({kPackageName, kPackageName2});

  app_host->OnInstallationStarted(kPackageName2);
  EXPECT_EQ(3, delegate()->add_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_STARTED,
            delegate()->last_event().event_type());
  EXPECT_EQ(kPackageName2, delegate()->last_request().package_name);
  EXPECT_TRUE(delegate()->last_request().add_disk_space_info);

  result.package_name = kPackageName2;
  result.success = false;
  app_host->OnInstallationFinished(
      arc::mojom::InstallationResultPtr(result.Clone()));
  EXPECT_EQ(4, delegate()->add_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_FAILED,
            delegate()->last_event().event_type());
  EXPECT_EQ(kPackageName2, delegate()->last_request().package_name);
  EXPECT_TRUE(delegate()->last_request().add_disk_space_info);
}

TEST_F(ArcAppInstallEventLogCollectorTest, OnPlayStoreLocalPolicySet) {
  std::unique_ptr<ArcAppInstallEventLogCollector> collector =
      std::make_unique<ArcAppInstallEventLogCollector>(delegate(), profile(),
                                                       packages_);
  base::Time time = base::Time::Now();
  collector->OnPlayStoreLocalPolicySet(time, packages_);
  ASSERT_EQ(1, delegate()->add_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::PLAYSTORE_LOCAL_POLICY_SET,
            delegate()->last_event().event_type());
  EXPECT_EQ(TimeToTimestamp(time), delegate()->requests()[0].event.timestamp());
  EXPECT_EQ(kPackageName, delegate()->last_request().package_name);
  EXPECT_TRUE(delegate()->last_request().add_disk_space_info);
}

TEST_F(ArcAppInstallEventLogCollectorTest,
       UpdatePolicySuccessRate_InstallSuccess) {
  std::unique_ptr<ArcAppInstallEventLogCollector> collector =
      std::make_unique<ArcAppInstallEventLogCollector>(delegate(), profile(),
                                                       packages_);
  collector->OnInstallationFinished(kPackageName, /*success=*/true,
                                    /*is_launchable_app=*/true);

  int second_to_last_request_index = delegate()->requests().size() - 2;
  EXPECT_EQ(1, delegate()->update_policy_success_rate_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_FINISHED,
            delegate()->event_at(second_to_last_request_index).event_type());
  EXPECT_EQ(kPackageName,
            delegate()->request_at(second_to_last_request_index).package_name);
}

TEST_F(ArcAppInstallEventLogCollectorTest,
       UpdatePolicySuccessRate_InstallFailure) {
  std::unique_ptr<ArcAppInstallEventLogCollector> collector =
      std::make_unique<ArcAppInstallEventLogCollector>(delegate(), profile(),
                                                       packages_);
  collector->OnInstallationFinished(kPackageName, /*success=*/false,
                                    /*is_launchable_app=*/false);

  int second_to_last_request_index = delegate()->requests().size() - 2;
  EXPECT_EQ(1, delegate()->update_policy_success_rate_count());
  EXPECT_EQ(em::AppInstallReportLogEvent::INSTALLATION_FAILED,
            delegate()->event_at(second_to_last_request_index).event_type());
  EXPECT_EQ(kPackageName,
            delegate()->request_at(second_to_last_request_index).package_name);
}

}  // namespace policy