chromium/chrome/browser/ash/policy/uploading/status_uploader_unittest.cc

// Copyright 2015 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/uploading/status_uploader.h"

#include <memory>
#include <tuple>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/gmock_move_support.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/ash/policy/core/device_local_account.h"
#include "chrome/browser/ash/policy/status_collector/device_status_collector.h"
#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
#include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/policy/core/common/device_local_account_type.h"
#include "components/prefs/testing_pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/user_activity/user_activity_detector.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/events/platform_event.h"

namespace policy {

namespace {

constexpr base::TimeDelta kDefaultStatusUploadDelay = base::Hours(1);
constexpr base::TimeDelta kMinImmediateUploadInterval = base::Seconds(10);
constexpr base::TimeDelta kMinimumScreenshotIdlenessCutoff = base::Minutes(5);

// Using a DeviceStatusCollector to have a concrete StatusCollector, but the
// exact type doesn't really matter, as it is being mocked.
class MockDeviceStatusCollector : public DeviceStatusCollector {
 public:
  explicit MockDeviceStatusCollector(PrefService* local_state)
      : DeviceStatusCollector(local_state, nullptr, nullptr, nullptr) {}
  MOCK_METHOD1(GetStatusAsync, void(StatusCollectorCallback));

  MOCK_METHOD0(OnSubmittedSuccessfully, void());

  // Explicit mock implementation declared here, since gmock::Invoke can't
  // handle returning non-moveable types like scoped_ptr.
  std::unique_ptr<DeviceLocalAccount> GetAutoLaunchedKioskSessionInfo()
      override {
    return std::make_unique<DeviceLocalAccount>(
        DeviceLocalAccountType::kKioskApp,
        policy::DeviceLocalAccount::EphemeralMode::kUnset, "account_id",
        "app_id", "update_url");
  }
};

}  // namespace

class StatusUploaderTest : public testing::Test {
 public:
  StatusUploaderTest() : task_runner_(new base::TestSimpleTaskRunner()) {
    DeviceStatusCollector::RegisterPrefs(prefs_.registry());
  }

  void SetUp() override {
    // Required for `DeviceStatusCollector`.
    chromeos::PowerManagerClient::InitializeFake();
    chromeos::TpmManagerClient::InitializeFake();
    client_.SetDMToken("dm_token");
    collector_ = std::make_unique<MockDeviceStatusCollector>(&prefs_);

    // Keep a pointer to the mock collector because collector_ gets cleared
    // when it is passed to the StatusUploader constructor.
    collector_ptr_ = collector_.get();
  }

  void TearDown() override {
    content::RunAllTasksUntilIdle();
    chromeos::TpmManagerClient::Shutdown();
    chromeos::PowerManagerClient::Shutdown();
  }

  // Given a pending task to upload status, runs the task and returns the
  // callback waiting to get device status / session status. The status upload
  // task will be blocked until the test code calls that callback.
  StatusCollectorCallback CollectStatusCallback() {
    // Running the task should pass a callback into
    // GetStatusAsync. We'll grab this callback.
    EXPECT_TRUE(task_runner_->HasPendingTask());
    StatusCollectorCallback status_callback;
    EXPECT_CALL(*collector_ptr_, GetStatusAsync)
        .WillOnce(MoveArg<0>(&status_callback));
    task_runner_->RunPendingTasks();

    return status_callback;
  }

  // Given a pending task to upload status, mocks out a server response.
  void RunPendingUploadTaskAndCheckNext(const StatusUploader& uploader,
                                        base::TimeDelta expected_delay,
                                        bool upload_success) {
    StatusCollectorCallback status_callback = CollectStatusCallback();

    // Running the status collected callback should trigger
    // CloudPolicyClient::UploadDeviceStatus.
    CloudPolicyClient::ResultCallback callback;
    EXPECT_CALL(client_, UploadDeviceStatus).WillOnce(MoveArg<3>(&callback));

    // Send some "valid" (read: non-nullptr) device/session data to the
    // callback in order to simulate valid status data.
    StatusCollectorParams status_params;
    std::move(status_callback).Run(std::move(status_params));

    // Make sure no status upload is queued up yet (since an upload is in
    // progress).
    EXPECT_FALSE(task_runner_->HasPendingTask());

    // StatusUpdater is only supposed to tell DeviceStatusCollector to clear its
    // caches if the status upload succeeded.
    EXPECT_CALL(*collector_ptr_, OnSubmittedSuccessfully())
        .Times(upload_success ? 1 : 0);

    // Now invoke the response.
    std::move(callback).Run(
        upload_success ? CloudPolicyClient::Result(DM_STATUS_SUCCESS)
                       : CloudPolicyClient::Result(DM_STATUS_REQUEST_FAILED));

    // Now that the previous request was satisfied, a task to do the next
    // upload should be queued.
    EXPECT_EQ(1U, task_runner_->NumPendingTasks());

    CheckPendingTaskDelay(uploader, expected_delay,
                          task_runner_->NextPendingTaskDelay());
  }

  void CheckPendingTaskDelay(const StatusUploader& uploader,
                             base::TimeDelta expected_delay,
                             base::TimeDelta task_delay) {
    // The next task should be scheduled sometime between |last_upload| +
    // |expected_delay| and |now| + |expected_delay|.
    base::Time now = base::Time::NowFromSystemTime();
    base::Time next_task = now + task_delay;

    EXPECT_LE(next_task, now + expected_delay);
    EXPECT_GE(next_task, uploader.last_upload() + expected_delay);
  }

  std::unique_ptr<StatusUploader> CreateStatusUploader() {
    return std::make_unique<StatusUploader>(&client_, std::move(collector_),
                                            task_runner_,
                                            kDefaultStatusUploadDelay);
  }

  void MockUserInput() {
    ui::MouseEvent e(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
                     ui::EventTimeForNow(), 0, 0);
    const ui::PlatformEvent& native_event = &e;
    ui::UserActivityDetector::Get()->DidProcessEvent(native_event);
  }

  content::BrowserTaskEnvironment task_environment_{
      content::BrowserTaskEnvironment::TimeSource::MOCK_TIME};
  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
  ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
  std::unique_ptr<MockDeviceStatusCollector> collector_;
  raw_ptr<MockDeviceStatusCollector, DanglingUntriaged> collector_ptr_;
  MockCloudPolicyClient client_;
  TestingPrefServiceSimple prefs_;
  // This property is required to instantiate the session manager, a singleton
  // which is used by the device status collector.
  session_manager::SessionManager session_manager_;
};

TEST_F(StatusUploaderTest, BasicTest) {
  EXPECT_FALSE(task_runner_->HasPendingTask());
  auto uploader = CreateStatusUploader();
  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
  // On startup, first update should happen in 1 minute.
  EXPECT_EQ(base::Minutes(1), task_runner_->NextPendingTaskDelay());
}

TEST_F(StatusUploaderTest, DifferentFrequencyAtStart) {
  const base::TimeDelta new_delay = kDefaultStatusUploadDelay * 2;

  scoped_testing_cros_settings_.device_settings()->SetInteger(
      ash::kReportUploadFrequency, new_delay.InMilliseconds());
  EXPECT_FALSE(task_runner_->HasPendingTask());
  auto uploader = CreateStatusUploader();
  ASSERT_EQ(1U, task_runner_->NumPendingTasks());
  // On startup, first update should happen in 1 minute.
  EXPECT_EQ(base::Minutes(1), task_runner_->NextPendingTaskDelay());

  // Second update should use the delay specified in settings.
  RunPendingUploadTaskAndCheckNext(*uploader, new_delay,
                                   true /* upload_success */);
}

TEST_F(StatusUploaderTest, ResetTimerAfterStatusCollection) {
  auto uploader = CreateStatusUploader();
  RunPendingUploadTaskAndCheckNext(*uploader, kDefaultStatusUploadDelay,
                                   true /* upload_success */);

  // Handle this response also, and ensure new task is queued.
  RunPendingUploadTaskAndCheckNext(*uploader, kDefaultStatusUploadDelay,
                                   true /* upload_success */);

  // Now that the previous request was satisfied, a task to do the next
  // upload should be queued again.
  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
}

TEST_F(StatusUploaderTest, ResetTimerAfterFailedStatusCollection) {
  auto uploader = CreateStatusUploader();

  // Running the queued task should pass a callback into
  // GetStatusAsync. We'll grab this callback and send nullptrs
  // to it in order to simulate failure to get status.
  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
  StatusCollectorCallback status_callback;
  EXPECT_CALL(*collector_ptr_, GetStatusAsync)
      .WillOnce(MoveArg<0>(&status_callback));
  task_runner_->RunPendingTasks();

  // Running the callback should trigger StatusUploader::OnStatusReceived, which
  // in turn should recognize the failure to get status and queue another status
  // upload.
  StatusCollectorParams status_params;
  status_params.device_status.reset();
  status_params.session_status.reset();
  status_params.child_status.reset();
  std::move(status_callback).Run(std::move(status_params));
  EXPECT_EQ(1U, task_runner_->NumPendingTasks());

  // Check the delay of the queued upload
  CheckPendingTaskDelay(*uploader, kDefaultStatusUploadDelay,
                        task_runner_->NextPendingTaskDelay());
}

TEST_F(StatusUploaderTest, ResetTimerAfterUploadError) {
  auto uploader = CreateStatusUploader();

  // Simulate upload error
  RunPendingUploadTaskAndCheckNext(*uploader, kDefaultStatusUploadDelay,
                                   false /* upload_success */);

  // Now that the previous request was satisfied, a task to do the next
  // upload should be queued again.
  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
}

TEST_F(StatusUploaderTest, ResetTimerAfterUnregisteredClient) {
  auto uploader = CreateStatusUploader();

  client_.SetDMToken("");
  EXPECT_FALSE(client_.is_registered());

  StatusCollectorCallback status_callback = CollectStatusCallback();

  // Running the status collected callback should trigger
  // CloudPolicyClient::UploadDeviceStatus.
  CloudPolicyClient::ResultCallback callback;
  EXPECT_CALL(client_, UploadDeviceStatus).WillOnce(MoveArg<3>(&callback));

  // Send some "valid" (read: non-nullptr) device/session data to the
  // callback in order to simulate valid status data.
  StatusCollectorParams status_params;
  std::move(status_callback).Run(std::move(status_params));

  // Make sure no status upload is queued up yet (since an upload is in
  // progress).
  EXPECT_FALSE(task_runner_->HasPendingTask());

  // No successful submit will happen if not registered.
  EXPECT_CALL(*collector_ptr_, OnSubmittedSuccessfully()).Times(0);

  // Now invoke the response.
  std::move(callback).Run(
      CloudPolicyClient::Result(CloudPolicyClient::NotRegistered()));

  // A task to try again should be queued.
  ASSERT_EQ(1U, task_runner_->NumPendingTasks());

  CheckPendingTaskDelay(*uploader, kDefaultStatusUploadDelay,
                        task_runner_->NextPendingTaskDelay());
}

TEST_F(StatusUploaderTest, ChangeFrequency) {
  auto uploader = CreateStatusUploader();
  // Change the frequency. The new frequency should be reflected in the timing
  // used for the next callback.
  const base::TimeDelta new_delay = kDefaultStatusUploadDelay * 2;
  scoped_testing_cros_settings_.device_settings()->SetInteger(
      ash::kReportUploadFrequency, new_delay.InMilliseconds());
  RunPendingUploadTaskAndCheckNext(*uploader, new_delay,
                                   true /* upload_success */);
}

TEST_F(StatusUploaderTest, ScreenshotUploadAllowOnlyAfterCutoffTime) {
  auto uploader = CreateStatusUploader();
  // Should allow data upload before there is user input.
  EXPECT_TRUE(uploader->IsScreenshotAllowed());

  MockUserInput();
  EXPECT_FALSE(uploader->IsScreenshotAllowed());

  MockUserInput();
  task_environment_.AdvanceClock(kMinimumScreenshotIdlenessCutoff -
                                 base::Seconds(1));
  EXPECT_FALSE(uploader->IsScreenshotAllowed());

  // Screenshot is allowed again after a period of inactivity
  MockUserInput();
  task_environment_.AdvanceClock(kMinimumScreenshotIdlenessCutoff +
                                 base::Seconds(1));
  EXPECT_TRUE(uploader->IsScreenshotAllowed());
}

TEST_F(StatusUploaderTest, NoUploadAfterVideoCapture) {
  auto uploader = CreateStatusUploader();
  // Should allow data upload before there is video capture.
  EXPECT_TRUE(uploader->IsScreenshotAllowed());

  // Now mock video capture, and no session data should be allowed.
  MediaCaptureDevicesDispatcher::GetInstance()->OnMediaRequestStateChanged(
      0, 0, 0, GURL("http://www.google.com"),
      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE,
      content::MEDIA_REQUEST_STATE_OPENING);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(uploader->IsScreenshotAllowed());
}

TEST_F(StatusUploaderTest, ScheduleImmediateStatusUpload) {
  EXPECT_FALSE(task_runner_->HasPendingTask());
  auto uploader = CreateStatusUploader();
  EXPECT_EQ(1U, task_runner_->NumPendingTasks());

  // On startup, first update should happen in 1 minute.
  EXPECT_EQ(base::Minutes(1), task_runner_->NextPendingTaskDelay());

  // Schedule an immediate status upload.
  uploader->ScheduleNextStatusUploadImmediately();
  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
  CheckPendingTaskDelay(*uploader, base::TimeDelta(),
                        task_runner_->FinalPendingTaskDelay());
}

TEST_F(StatusUploaderTest, ScheduleImmediateStatusUploadConsecutively) {
  EXPECT_FALSE(task_runner_->HasPendingTask());
  auto uploader = CreateStatusUploader();
  EXPECT_EQ(1U, task_runner_->NumPendingTasks());

  // On startup, first update should happen in 1 minute.
  EXPECT_EQ(base::Minutes(1), task_runner_->NextPendingTaskDelay());

  // Schedule an immediate status upload and run it.
  uploader->ScheduleNextStatusUploadImmediately();
  RunPendingUploadTaskAndCheckNext(*uploader, kDefaultStatusUploadDelay,
                                   true /* upload_success */);

  // Schedule the next one and check that it was scheduled after
  // kMinImmediateUploadInterval of the last upload.
  uploader->ScheduleNextStatusUploadImmediately();
  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
  CheckPendingTaskDelay(*uploader, kMinImmediateUploadInterval,
                        task_runner_->FinalPendingTaskDelay());
}

}  // namespace policy