root/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. SetMockPositionToReturnNext
  2. MockPositionUpdateRequester
  3. Simulate
  4. set_max_stored_past_activity_days
  5. set_max_stored_future_activity_days
  6. SetBaselineTime
  7. CheckIdleState
  8. GetCurrentTime
  9. GetActiveMilliseconds
  10. user_manager_enabler_
  11. RestartStatusCollector
  12. GetStatus
  13. CheckThatNoLocationIsReported
  14. CheckThatAValidLocationIsReported
  15. CheckThatALocationErrorIsReported
  16. ActivePeriodMilliseconds
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F
  21. TEST_F
  22. TEST_F
  23. TEST_F
  24. TEST_F
  25. TEST_F
  26. TEST_F
  27. TEST_F
  28. TEST_F
  29. TEST_F
  30. TEST_F
  31. SetUp
  32. TearDown
  33. TEST_F

// Copyright (c) 2012 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/device_status_collector.h"

#include "base/environment.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/testing_pref_service.h"
#include "base/run_loop.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/chromeos/login/mock_user_manager.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/policy/stub_enterprise_install_attributes.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/chromeos/settings/device_settings_service.h"
#include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/shill_device_client.h"
#include "chromeos/network/network_handler.h"
#include "chromeos/settings/cros_settings_names.h"
#include "chromeos/settings/cros_settings_provider.h"
#include "chromeos/system/mock_statistics_provider.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/geolocation_provider.h"
#include "content/public/test/test_browser_thread.h"
#include "content/public/test/test_utils.h"
#include "policy/proto/device_management_backend.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

using ::testing::DoAll;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::_;
using base::Time;
using base::TimeDelta;

namespace em = enterprise_management;

namespace {

const int64 kMillisecondsPerDay = Time::kMicrosecondsPerDay / 1000;

scoped_ptr<content::Geoposition> mock_position_to_return_next;

void SetMockPositionToReturnNext(const content::Geoposition &position) {
  mock_position_to_return_next.reset(new content::Geoposition(position));
}

void MockPositionUpdateRequester(
    const content::GeolocationProvider::LocationUpdateCallback& callback) {
  if (!mock_position_to_return_next.get())
    return;

  // If the fix is invalid, the DeviceStatusCollector will immediately request
  // another update when it receives the callback. This is desirable and safe in
  // real life where geolocation updates arrive asynchronously. In this testing
  // harness, the callback is invoked synchronously upon request, leading to a
  // request-callback loop. The loop is broken by returning the mock position
  // only once.
  scoped_ptr<content::Geoposition> position(
      mock_position_to_return_next.release());
  callback.Run(*position);
}

class TestingDeviceStatusCollector : public policy::DeviceStatusCollector {
 public:
  TestingDeviceStatusCollector(
      PrefService* local_state,
      chromeos::system::StatisticsProvider* provider,
      policy::DeviceStatusCollector::LocationUpdateRequester*
          location_update_requester)
      : policy::DeviceStatusCollector(
          local_state,
          provider,
          location_update_requester) {
    // Set the baseline time to a fixed value (1 AM) to prevent test flakiness
    // due to a single activity period spanning two days.
    SetBaselineTime(Time::Now().LocalMidnight() + TimeDelta::FromHours(1));
  }

  void Simulate(IdleState* states, int len) {
    for (int i = 0; i < len; i++)
      IdleStateCallback(states[i]);
  }

  void set_max_stored_past_activity_days(unsigned int value) {
    max_stored_past_activity_days_ = value;
  }

  void set_max_stored_future_activity_days(unsigned int value) {
    max_stored_future_activity_days_ = value;
  }

  // Reset the baseline time.
  void SetBaselineTime(Time time) {
    baseline_time_ = time;
    baseline_offset_periods_ = 0;
  }

 protected:
  virtual void CheckIdleState() OVERRIDE {
    // This should never be called in testing, as it results in a dbus call.
    ADD_FAILURE();
  }

  // Each time this is called, returns a time that is a fixed increment
  // later than the previous time.
  virtual Time GetCurrentTime() OVERRIDE {
    int poll_interval = policy::DeviceStatusCollector::kIdlePollIntervalSeconds;
    return baseline_time_ +
        TimeDelta::FromSeconds(poll_interval * baseline_offset_periods_++);
  }

 private:
  // Baseline time for the fake times returned from GetCurrentTime().
  Time baseline_time_;

  // The number of simulated periods since the baseline time.
  int baseline_offset_periods_;
};

// Return the total number of active milliseconds contained in a device
// status report.
int64 GetActiveMilliseconds(em::DeviceStatusReportRequest& status) {
  int64 active_milliseconds = 0;
  for (int i = 0; i < status.active_period_size(); i++) {
    active_milliseconds += status.active_period(i).active_duration();
  }
  return active_milliseconds;
}

}  // namespace

namespace policy {

// Though it is a unit test, this test is linked with browser_tests so that it
// runs in a separate process. The intention is to avoid overriding the timezone
// environment variable for other tests.
class DeviceStatusCollectorTest : public testing::Test {
 public:
  DeviceStatusCollectorTest()
    : ui_thread_(content::BrowserThread::UI, &message_loop_),
      file_thread_(content::BrowserThread::FILE, &message_loop_),
      io_thread_(content::BrowserThread::IO, &message_loop_),
      user_manager_(new chromeos::MockUserManager()),
      user_manager_enabler_(user_manager_) {
    // Run this test with a well-known timezone so that Time::LocalMidnight()
    // returns the same values on all machines.
    scoped_ptr<base::Environment> env(base::Environment::Create());
    env->SetVar("TZ", "UTC");

    TestingDeviceStatusCollector::RegisterPrefs(prefs_.registry());

    EXPECT_CALL(statistics_provider_, GetMachineStatistic(_, NotNull()))
        .WillRepeatedly(Return(false));

    // Remove the real DeviceSettingsProvider and replace it with a stub.
    cros_settings_ = chromeos::CrosSettings::Get();
    device_settings_provider_ =
        cros_settings_->GetProvider(chromeos::kReportDeviceVersionInfo);
    EXPECT_TRUE(device_settings_provider_ != NULL);
    EXPECT_TRUE(
        cros_settings_->RemoveSettingsProvider(device_settings_provider_));
    cros_settings_->AddSettingsProvider(&stub_settings_provider_);

    // Set up fake install attributes.
    StubEnterpriseInstallAttributes* attributes =
        new StubEnterpriseInstallAttributes();
    attributes->SetDomain("managed.com");
    attributes->SetRegistrationUser("user@managed.com");
    BrowserPolicyConnectorChromeOS::SetInstallAttributesForTesting(attributes);

    RestartStatusCollector();
  }

  virtual ~DeviceStatusCollectorTest() {
    // Finish pending tasks.
    content::BrowserThread::GetBlockingPool()->FlushForTesting();
    message_loop_.RunUntilIdle();

    // Restore the real DeviceSettingsProvider.
    EXPECT_TRUE(
      cros_settings_->RemoveSettingsProvider(&stub_settings_provider_));
    cros_settings_->AddSettingsProvider(device_settings_provider_);
  }

  void RestartStatusCollector() {
    policy::DeviceStatusCollector::LocationUpdateRequester callback =
        base::Bind(&MockPositionUpdateRequester);
    status_collector_.reset(
        new TestingDeviceStatusCollector(&prefs_,
                                         &statistics_provider_,
                                         &callback));
  }

  void GetStatus() {
    status_.Clear();
    status_collector_->GetDeviceStatus(&status_);
  }

  void CheckThatNoLocationIsReported() {
    GetStatus();
    EXPECT_FALSE(status_.has_device_location());
  }

  void CheckThatAValidLocationIsReported() {
    // Checks that a location is being reported which matches the valid fix
    // set using SetMockPositionToReturnNext().
    GetStatus();
    EXPECT_TRUE(status_.has_device_location());
    em::DeviceLocation location = status_.device_location();
    if (location.has_error_code())
      EXPECT_EQ(em::DeviceLocation::ERROR_CODE_NONE, location.error_code());
    EXPECT_TRUE(location.has_latitude());
    EXPECT_TRUE(location.has_longitude());
    EXPECT_TRUE(location.has_accuracy());
    EXPECT_TRUE(location.has_timestamp());
    EXPECT_FALSE(location.has_altitude());
    EXPECT_FALSE(location.has_altitude_accuracy());
    EXPECT_FALSE(location.has_heading());
    EXPECT_FALSE(location.has_speed());
    EXPECT_FALSE(location.has_error_message());
    EXPECT_DOUBLE_EQ(4.3, location.latitude());
    EXPECT_DOUBLE_EQ(-7.8, location.longitude());
    EXPECT_DOUBLE_EQ(3., location.accuracy());
    // Check that the timestamp is not older than ten minutes.
    EXPECT_TRUE(Time::Now() - Time::FromDoubleT(location.timestamp() / 1000.) <
                TimeDelta::FromMinutes(10));
  }

  void CheckThatALocationErrorIsReported() {
    GetStatus();
    EXPECT_TRUE(status_.has_device_location());
    em::DeviceLocation location = status_.device_location();
    EXPECT_TRUE(location.has_error_code());
    EXPECT_EQ(em::DeviceLocation::ERROR_CODE_POSITION_UNAVAILABLE,
              location.error_code());
  }

 protected:
  // Convenience method.
  int64 ActivePeriodMilliseconds() {
    return policy::DeviceStatusCollector::kIdlePollIntervalSeconds * 1000;
  }

  // Since this is a unit test running in browser_tests we must do additional
  // unit test setup and make a TestingBrowserProcess. Must be first member.
  TestingBrowserProcessInitializer initializer_;
  base::MessageLoopForUI message_loop_;
  content::TestBrowserThread ui_thread_;
  content::TestBrowserThread file_thread_;
  content::TestBrowserThread io_thread_;

  TestingPrefServiceSimple prefs_;
  chromeos::system::MockStatisticsProvider statistics_provider_;
  chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
  chromeos::ScopedTestCrosSettings test_cros_settings_;
  chromeos::CrosSettings* cros_settings_;
  chromeos::CrosSettingsProvider* device_settings_provider_;
  chromeos::StubCrosSettingsProvider stub_settings_provider_;
  chromeos::MockUserManager* user_manager_;
  chromeos::ScopedUserManagerEnabler user_manager_enabler_;
  em::DeviceStatusReportRequest status_;
  scoped_ptr<TestingDeviceStatusCollector> status_collector_;
};

TEST_F(DeviceStatusCollectorTest, AllIdle) {
  IdleState test_states[] = {
    IDLE_STATE_IDLE,
    IDLE_STATE_IDLE,
    IDLE_STATE_IDLE
  };
  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);

  // Test reporting with no data.
  GetStatus();
  EXPECT_EQ(0, status_.active_period_size());
  EXPECT_EQ(0, GetActiveMilliseconds(status_));

  // Test reporting with a single idle sample.
  status_collector_->Simulate(test_states, 1);
  GetStatus();
  EXPECT_EQ(0, status_.active_period_size());
  EXPECT_EQ(0, GetActiveMilliseconds(status_));

  // Test reporting with multiple consecutive idle samples.
  status_collector_->Simulate(test_states,
                              sizeof(test_states) / sizeof(IdleState));
  GetStatus();
  EXPECT_EQ(0, status_.active_period_size());
  EXPECT_EQ(0, GetActiveMilliseconds(status_));
}

TEST_F(DeviceStatusCollectorTest, AllActive) {
  IdleState test_states[] = {
    IDLE_STATE_ACTIVE,
    IDLE_STATE_ACTIVE,
    IDLE_STATE_ACTIVE
  };
  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);

  // Test a single active sample.
  status_collector_->Simulate(test_states, 1);
  GetStatus();
  EXPECT_EQ(1, status_.active_period_size());
  EXPECT_EQ(1 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
  status_.clear_active_period(); // Clear the result protobuf.

  // Test multiple consecutive active samples.
  status_collector_->Simulate(test_states,
                              sizeof(test_states) / sizeof(IdleState));
  GetStatus();
  EXPECT_EQ(1, status_.active_period_size());
  EXPECT_EQ(4 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
}

TEST_F(DeviceStatusCollectorTest, MixedStates) {
  IdleState test_states[] = {
    IDLE_STATE_ACTIVE,
    IDLE_STATE_IDLE,
    IDLE_STATE_ACTIVE,
    IDLE_STATE_ACTIVE,
    IDLE_STATE_IDLE,
    IDLE_STATE_IDLE,
    IDLE_STATE_ACTIVE
  };
  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
  status_collector_->Simulate(test_states,
                              sizeof(test_states) / sizeof(IdleState));
  GetStatus();
  EXPECT_EQ(4 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
}

TEST_F(DeviceStatusCollectorTest, StateKeptInPref) {
  IdleState test_states[] = {
    IDLE_STATE_ACTIVE,
    IDLE_STATE_IDLE,
    IDLE_STATE_ACTIVE,
    IDLE_STATE_ACTIVE,
    IDLE_STATE_IDLE,
    IDLE_STATE_IDLE
  };
  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
  status_collector_->Simulate(test_states,
                              sizeof(test_states) / sizeof(IdleState));

  // Process the list a second time after restarting the collector. It should be
  // able to count the active periods found by the original collector, because
  // the results are stored in a pref.
  RestartStatusCollector();
  status_collector_->Simulate(test_states,
                              sizeof(test_states) / sizeof(IdleState));

  GetStatus();
  EXPECT_EQ(6 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
}

TEST_F(DeviceStatusCollectorTest, Times) {
  IdleState test_states[] = {
    IDLE_STATE_ACTIVE,
    IDLE_STATE_IDLE,
    IDLE_STATE_ACTIVE,
    IDLE_STATE_ACTIVE,
    IDLE_STATE_IDLE,
    IDLE_STATE_IDLE
  };
  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
  status_collector_->Simulate(test_states,
                              sizeof(test_states) / sizeof(IdleState));
  GetStatus();
  EXPECT_EQ(3 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
}

TEST_F(DeviceStatusCollectorTest, MaxStoredPeriods) {
  IdleState test_states[] = {
    IDLE_STATE_ACTIVE,
    IDLE_STATE_IDLE
  };
  const int kMaxDays = 10;

  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);
  status_collector_->set_max_stored_past_activity_days(kMaxDays - 1);
  status_collector_->set_max_stored_future_activity_days(1);
  Time baseline = Time::Now().LocalMidnight();

  // Simulate 12 active periods.
  for (int i = 0; i < kMaxDays + 2; i++) {
    status_collector_->Simulate(test_states,
                                sizeof(test_states) / sizeof(IdleState));
    // Advance the simulated clock by a day.
    baseline += TimeDelta::FromDays(1);
    status_collector_->SetBaselineTime(baseline);
  }

  // Check that we don't exceed the max number of periods.
  GetStatus();
  EXPECT_EQ(kMaxDays - 1, status_.active_period_size());

  // Simulate some future times.
  for (int i = 0; i < kMaxDays + 2; i++) {
    status_collector_->Simulate(test_states,
                                sizeof(test_states) / sizeof(IdleState));
    // Advance the simulated clock by a day.
    baseline += TimeDelta::FromDays(1);
    status_collector_->SetBaselineTime(baseline);
  }
  // Set the clock back so the previous simulated times are in the future.
  baseline -= TimeDelta::FromDays(20);
  status_collector_->SetBaselineTime(baseline);

  // Collect one more data point to trigger pruning.
  status_collector_->Simulate(test_states, 1);

  // Check that we don't exceed the max number of periods.
  status_.clear_active_period();
  GetStatus();
  EXPECT_LT(status_.active_period_size(), kMaxDays);
}

TEST_F(DeviceStatusCollectorTest, ActivityTimesDisabledByDefault) {
  // If the pref for collecting device activity times isn't explicitly turned
  // on, no data on activity times should be reported.

  IdleState test_states[] = {
    IDLE_STATE_ACTIVE,
    IDLE_STATE_ACTIVE,
    IDLE_STATE_ACTIVE
  };
  status_collector_->Simulate(test_states,
                              sizeof(test_states) / sizeof(IdleState));
  GetStatus();
  EXPECT_EQ(0, status_.active_period_size());
  EXPECT_EQ(0, GetActiveMilliseconds(status_));
}

TEST_F(DeviceStatusCollectorTest, ActivityCrossingMidnight) {
  IdleState test_states[] = {
    IDLE_STATE_ACTIVE
  };
  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);

  // Set the baseline time to 10 seconds after midnight.
  status_collector_->SetBaselineTime(
      Time::Now().LocalMidnight() + TimeDelta::FromSeconds(10));

  status_collector_->Simulate(test_states, 1);
  GetStatus();
  ASSERT_EQ(2, status_.active_period_size());

  em::ActiveTimePeriod period0 = status_.active_period(0);
  em::ActiveTimePeriod period1 = status_.active_period(1);
  EXPECT_EQ(ActivePeriodMilliseconds() - 10000, period0.active_duration());
  EXPECT_EQ(10000, period1.active_duration());

  em::TimePeriod time_period0 = period0.time_period();
  em::TimePeriod time_period1 = period1.time_period();

  EXPECT_EQ(time_period0.end_timestamp(), time_period1.start_timestamp());

  // Ensure that the start and end times for the period are a day apart.
  EXPECT_EQ(time_period0.end_timestamp() - time_period0.start_timestamp(),
            kMillisecondsPerDay);
  EXPECT_EQ(time_period1.end_timestamp() - time_period1.start_timestamp(),
            kMillisecondsPerDay);
}

TEST_F(DeviceStatusCollectorTest, ActivityTimesKeptUntilSubmittedSuccessfully) {
  IdleState test_states[] = {
    IDLE_STATE_ACTIVE,
    IDLE_STATE_ACTIVE,
  };
  cros_settings_->SetBoolean(chromeos::kReportDeviceActivityTimes, true);

  status_collector_->Simulate(test_states, 2);
  GetStatus();
  EXPECT_EQ(2 * ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
  em::DeviceStatusReportRequest first_status(status_);

  // The collector returns the same status again.
  GetStatus();
  EXPECT_EQ(first_status.SerializeAsString(), status_.SerializeAsString());

  // After indicating a successful submit, the submitted status gets cleared,
  // but what got collected meanwhile sticks around.
  status_collector_->Simulate(test_states, 1);
  status_collector_->OnSubmittedSuccessfully();
  GetStatus();
  EXPECT_EQ(ActivePeriodMilliseconds(), GetActiveMilliseconds(status_));
}

TEST_F(DeviceStatusCollectorTest, DevSwitchBootMode) {
  // Test that boot mode data is not reported if the pref is not turned on.
  EXPECT_CALL(statistics_provider_,
              GetMachineStatistic("devsw_boot", NotNull()))
      .WillRepeatedly(DoAll(SetArgPointee<1>("0"), Return(true)));
  GetStatus();
  EXPECT_FALSE(status_.has_boot_mode());

  // Turn the pref on, and check that the status is reported iff the
  // statistics provider returns valid data.
  cros_settings_->SetBoolean(chromeos::kReportDeviceBootMode, true);

  EXPECT_CALL(statistics_provider_,
              GetMachineStatistic("devsw_boot", NotNull()))
      .WillOnce(DoAll(SetArgPointee<1>("(error)"), Return(true)));
  GetStatus();
  EXPECT_FALSE(status_.has_boot_mode());

  EXPECT_CALL(statistics_provider_,
              GetMachineStatistic("devsw_boot", NotNull()))
      .WillOnce(DoAll(SetArgPointee<1>(" "), Return(true)));
  GetStatus();
  EXPECT_FALSE(status_.has_boot_mode());

  EXPECT_CALL(statistics_provider_,
              GetMachineStatistic("devsw_boot", NotNull()))
      .WillOnce(DoAll(SetArgPointee<1>("0"), Return(true)));
  GetStatus();
  EXPECT_EQ("Verified", status_.boot_mode());

  EXPECT_CALL(statistics_provider_,
              GetMachineStatistic("devsw_boot", NotNull()))
      .WillOnce(DoAll(SetArgPointee<1>("1"), Return(true)));
  GetStatus();
  EXPECT_EQ("Dev", status_.boot_mode());
}

TEST_F(DeviceStatusCollectorTest, VersionInfo) {
  // When the pref to collect this data is not enabled, expect that none of
  // the fields are present in the protobuf.
  GetStatus();
  EXPECT_FALSE(status_.has_browser_version());
  EXPECT_FALSE(status_.has_os_version());
  EXPECT_FALSE(status_.has_firmware_version());

  cros_settings_->SetBoolean(chromeos::kReportDeviceVersionInfo, true);
  GetStatus();
  EXPECT_TRUE(status_.has_browser_version());
  EXPECT_TRUE(status_.has_os_version());
  EXPECT_TRUE(status_.has_firmware_version());

  // Check that the browser version is not empty. OS version & firmware
  // don't have any reasonable values inside the unit test, so those
  // aren't checked.
  EXPECT_NE("", status_.browser_version());
}

TEST_F(DeviceStatusCollectorTest, Location) {
  content::Geoposition valid_fix;
  valid_fix.latitude = 4.3;
  valid_fix.longitude = -7.8;
  valid_fix.accuracy = 3.;
  valid_fix.timestamp = Time::Now();

  content::Geoposition invalid_fix;
  invalid_fix.error_code =
      content::Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
  invalid_fix.timestamp = Time::Now();

  // Check that when device location reporting is disabled, no location is
  // reported.
  SetMockPositionToReturnNext(valid_fix);
  CheckThatNoLocationIsReported();

  // Check that when device location reporting is enabled and a valid fix is
  // available, the location is reported and is stored in local state.
  SetMockPositionToReturnNext(valid_fix);
  cros_settings_->SetBoolean(chromeos::kReportDeviceLocation, true);
  EXPECT_FALSE(prefs_.GetDictionary(prefs::kDeviceLocation)->empty());
  CheckThatAValidLocationIsReported();

  // Restart the status collector. Check that the last known location has been
  // retrieved from local state without requesting a geolocation update.
  SetMockPositionToReturnNext(valid_fix);
  RestartStatusCollector();
  CheckThatAValidLocationIsReported();
  EXPECT_TRUE(mock_position_to_return_next.get());

  // Check that after disabling location reporting again, the last known
  // location has been cleared from local state and is no longer reported.
  SetMockPositionToReturnNext(valid_fix);
  cros_settings_->SetBoolean(chromeos::kReportDeviceLocation, false);
  // Allow the new pref to propagate to the status collector.
  message_loop_.RunUntilIdle();
  EXPECT_TRUE(prefs_.GetDictionary(prefs::kDeviceLocation)->empty());
  CheckThatNoLocationIsReported();

  // Check that after enabling location reporting again, an error is reported
  // if no valid fix is available.
  SetMockPositionToReturnNext(invalid_fix);
  cros_settings_->SetBoolean(chromeos::kReportDeviceLocation, true);
  // Allow the new pref to propagate to the status collector.
  message_loop_.RunUntilIdle();
  CheckThatALocationErrorIsReported();
}

TEST_F(DeviceStatusCollectorTest, ReportUsers) {
  user_manager_->CreatePublicAccountUser("public@localhost");
  user_manager_->AddUser("user0@managed.com");
  user_manager_->AddUser("user1@managed.com");
  user_manager_->AddUser("user2@managed.com");
  user_manager_->AddUser("user3@unmanaged.com");
  user_manager_->AddUser("user4@managed.com");
  user_manager_->AddUser("user5@managed.com");

  // Verify that users are not reported by default.
  GetStatus();
  EXPECT_EQ(0, status_.user_size());

  // Verify that users are reported after enabling the setting.
  cros_settings_->SetBoolean(chromeos::kReportDeviceUsers, true);
  GetStatus();
  EXPECT_EQ(5, status_.user_size());
  EXPECT_EQ(em::DeviceUser::USER_TYPE_MANAGED, status_.user(0).type());
  EXPECT_EQ("user0@managed.com", status_.user(0).email());
  EXPECT_EQ(em::DeviceUser::USER_TYPE_MANAGED, status_.user(1).type());
  EXPECT_EQ("user1@managed.com", status_.user(1).email());
  EXPECT_EQ(em::DeviceUser::USER_TYPE_MANAGED, status_.user(2).type());
  EXPECT_EQ("user2@managed.com", status_.user(2).email());
  EXPECT_EQ(em::DeviceUser::USER_TYPE_UNMANAGED, status_.user(3).type());
  EXPECT_FALSE(status_.user(3).has_email());
  EXPECT_EQ(em::DeviceUser::USER_TYPE_MANAGED, status_.user(4).type());
  EXPECT_EQ("user4@managed.com", status_.user(4).email());

  // Verify that users are no longer reported if setting is disabled.
  cros_settings_->SetBoolean(chromeos::kReportDeviceUsers, false);
  GetStatus();
  EXPECT_EQ(0, status_.user_size());
}

TEST_F(DeviceStatusCollectorTest, ReportManagedUser) {
  // Verify that at least one managed user is reported regardless of list size.
  user_manager_->AddUser("user0@unmanaged.com");
  user_manager_->AddUser("user1@unmanaged.com");
  user_manager_->AddUser("user2@unmanaged.com");
  user_manager_->AddUser("user3@unmanaged.com");
  user_manager_->AddUser("user4@unmanaged.com");
  user_manager_->AddUser("user5@unmanaged.com");
  user_manager_->AddUser("user6@managed.com");
  user_manager_->AddUser("user7@managed.com");

  cros_settings_->SetBoolean(chromeos::kReportDeviceUsers, true);
  GetStatus();
  EXPECT_EQ(7, status_.user_size());
  for (int i = 0; i < 6; ++i)
    EXPECT_EQ(em::DeviceUser::USER_TYPE_UNMANAGED, status_.user(i).type());
  EXPECT_EQ(em::DeviceUser::USER_TYPE_MANAGED, status_.user(6).type());
  EXPECT_EQ("user6@managed.com", status_.user(6).email());
}

// Fake device state.
struct FakeDeviceData {
  const char* device_path;
  const char* type;
  const char* object_path;
  const char* mac_address;
  const char* meid;
  const char* imei;
  int expected_type; // proto enum type value, -1 for not present.
};

static const FakeDeviceData kFakeDevices[] = {
  { "/device/ethernet", shill::kTypeEthernet, "ethernet",
    "112233445566", "", "",
    em::NetworkInterface::TYPE_ETHERNET },
  { "/device/cellular1", shill::kTypeCellular, "cellular1",
    "abcdefabcdef", "A10000009296F2", "",
    em::NetworkInterface::TYPE_CELLULAR },
  { "/device/cellular2", shill::kTypeCellular, "cellular2",
    "abcdefabcdef", "", "352099001761481",
    em::NetworkInterface::TYPE_CELLULAR },
  { "/device/wifi", shill::kTypeWifi, "wifi",
    "aabbccddeeff", "", "",
    em::NetworkInterface::TYPE_WIFI },
  { "/device/bluetooth", shill::kTypeBluetooth, "bluetooth",
    "", "", "",
    em::NetworkInterface::TYPE_BLUETOOTH },
  { "/device/vpn", shill::kTypeVPN, "vpn",
    "", "", "",
    -1 },
};

class DeviceStatusCollectorNetworkInterfacesTest
    : public DeviceStatusCollectorTest {
 protected:
  virtual void SetUp() OVERRIDE {
    chromeos::DBusThreadManager::InitializeWithStub();
    chromeos::NetworkHandler::Initialize();
    chromeos::ShillDeviceClient::TestInterface* test_device_client =
        chromeos::DBusThreadManager::Get()->GetShillDeviceClient()->
            GetTestInterface();
    test_device_client->ClearDevices();
    for (size_t i = 0; i < arraysize(kFakeDevices); ++i) {
      const FakeDeviceData& dev = kFakeDevices[i];
      test_device_client->AddDevice(dev.device_path, dev.type,
                                    dev.object_path);
      if (*dev.mac_address) {
        test_device_client->SetDeviceProperty(
            dev.device_path, shill::kAddressProperty,
            base::StringValue(dev.mac_address));
      }
      if (*dev.meid) {
        test_device_client->SetDeviceProperty(
            dev.device_path, shill::kMeidProperty,
            base::StringValue(dev.meid));
      }
      if (*dev.imei) {
        test_device_client->SetDeviceProperty(
            dev.device_path, shill::kImeiProperty,
            base::StringValue(dev.imei));
      }
    }

    // Flush out pending state updates.
    base::RunLoop().RunUntilIdle();
  }

  virtual void TearDown() OVERRIDE {
    chromeos::NetworkHandler::Shutdown();
    chromeos::DBusThreadManager::Shutdown();
  }
};

TEST_F(DeviceStatusCollectorNetworkInterfacesTest, NetworkInterfaces) {
  // No interfaces should be reported if the policy is off.
  GetStatus();
  EXPECT_EQ(0, status_.network_interface_size());

  // Switch the policy on and verify the interface list is present.
  cros_settings_->SetBoolean(chromeos::kReportDeviceNetworkInterfaces, true);
  GetStatus();

  int count = 0;
  for (size_t i = 0; i < arraysize(kFakeDevices); ++i) {
    const FakeDeviceData& dev = kFakeDevices[i];
    if (dev.expected_type == -1)
      continue;

    // Find the corresponding entry in reporting data.
    bool found_match = false;
    google::protobuf::RepeatedPtrField<em::NetworkInterface>::const_iterator
        iface;
    for (iface = status_.network_interface().begin();
         iface != status_.network_interface().end();
         ++iface) {
      // Check whether type, field presence and field values match.
      if (dev.expected_type == iface->type() &&
          iface->has_mac_address() == !!*dev.mac_address &&
          iface->has_meid() == !!*dev.meid &&
          iface->has_imei() == !!*dev.imei &&
          iface->mac_address() == dev.mac_address &&
          iface->meid() == dev.meid &&
          iface->imei() == dev.imei) {
        found_match = true;
        break;
      }
    }

    EXPECT_TRUE(found_match) << "No matching interface for fake device " << i;
    count++;
  }

  EXPECT_EQ(count, status_.network_interface_size());
}

}  // namespace policy

/* [<][>][^][v][top][bottom][index][help] */