chromium/chrome/browser/ash/policy/remote_commands/crd/device_command_start_crd_session_job_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/remote_commands/crd/device_command_start_crd_session_job.h"

#include <memory>
#include <optional>
#include <utility>

#include "base/check_deref.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/test/values_test_util.h"
#include "base/time/time.h"
#include "chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h"
#include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_manager.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.h"
#include "chrome/browser/ash/policy/remote_commands/crd/fake_start_crd_session_job_delegate.h"
#include "chrome/browser/ash/policy/remote_commands/fake_cros_network_config.h"
#include "chrome/browser/ash/policy/remote_commands/user_session_type_test_util.h"
#include "chrome/browser/ash/settings/device_settings_test_helper.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_manager.h"
#include "chromeos/ash/services/network_config/in_process_instance.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/scoped_user_manager.h"
#include "remoting/host/chromeos/features.h"
#include "ui/base/user_activity/user_activity_detector.h"

namespace policy {

namespace {

using base::test::IsJson;
using base::test::TestFuture;
using chromeos::network_config::mojom::NetworkType;
using chromeos::network_config::mojom::OncSource;
using remoting::features::kEnableCrdAdminRemoteAccess;
using remoting::features::kEnableCrdAdminRemoteAccessV2;
using remoting::features::kEnableCrdFileTransferForKiosk;
using test::TestSessionType;

using Payload = base::Value::Dict;

namespace em = ::enterprise_management;

constexpr char kResultCodeFieldName[] = "resultCode";
constexpr char kResultMessageFieldName[] = "message";
constexpr char kResultAccessCodeFieldName[] = "accessCode";
constexpr char kResultLastActivityFieldName[] = "lastActivitySec";

constexpr RemoteCommandJob::UniqueIDType kUniqueID = 123456789;

// Common template used in all UMA histograms for session result logs.
constexpr char kHistogramResultTemplate[] =
    "Enterprise.DeviceRemoteCommand.Crd.%s.%s.Result";
// Common template used in all UMA histograms for session duration logs.
constexpr char kHistogramDurationTemplate[] =
    "Enterprise.DeviceRemoteCommand.Crd.%s.%s.SessionDuration";

// Created for session type logged to UMA.
const char* SessionTypeToUmaString(TestSessionType session_type) {
  switch (session_type) {
    case TestSessionType::kManuallyLaunchedWebKioskSession:
    case TestSessionType::kManuallyLaunchedKioskSession:
      return "ManuallyLaunchedKioskSession";
    case TestSessionType::kAutoLaunchedWebKioskSession:
    case TestSessionType::kAutoLaunchedKioskSession:
      return "AutoLaunchedKioskSession";
    case TestSessionType::kManagedGuestSession:
      return "ManagedGuestSession";
    case TestSessionType::kAffiliatedUserSession:
      return "AffiliatedUserSession";
    case TestSessionType::kGuestSession:
      return "GuestSession";
    case TestSessionType::kUnaffiliatedUserSession:
      return "UnaffiliatedUserSession";
    case TestSessionType::kNoSession:
      return "NoUserSession";
  }
}

// Macro expecting success. We are using a macro because a function would
// report any error against the line in the function, and not against the
// place where EXPECT_SUCCESS is called.
#define EXPECT_SUCCESS(statement_)                                      \
  ({                                                                    \
    auto result_ = statement_;                                          \
    EXPECT_EQ(result_.status, RemoteCommandJob::Status::SUCCEEDED);     \
    EXPECT_THAT(result_.payload,                                        \
                IsJson(CreateSuccessPayload(                            \
                    FakeStartCrdSessionJobDelegate::kTestAccessCode))); \
  })

// Macro expecting error. We are using a macro because a function would
// report any error against the line in the function, and not against the
// place where EXPECT_ERROR is called.
#define EXPECT_ERROR(statement_, error_code, ...)                       \
  ({                                                                    \
    auto result_ = statement_;                                          \
    EXPECT_EQ(result_.status, RemoteCommandJob::Status::FAILED);        \
    EXPECT_THAT(result_.payload,                                        \
                IsJson(CreateErrorPayload(error_code, ##__VA_ARGS__))); \
  })

em::RemoteCommand GenerateCommandProto(RemoteCommandJob::UniqueIDType unique_id,
                                       base::TimeDelta age_of_command,
                                       const std::string& payload) {
  em::RemoteCommand command_proto;
  command_proto.set_type(em::RemoteCommand_Type_DEVICE_START_CRD_SESSION);
  command_proto.set_command_id(unique_id);
  command_proto.set_age_of_command(age_of_command.InMilliseconds());
  command_proto.set_payload(payload);
  return command_proto;
}

test::NetworkBuilder CreateNetwork(NetworkType type = NetworkType::kWiFi) {
  return test::NetworkBuilder(type);
}

// Returns true if the given session type supports a 'remote support' session.
bool SupportsRemoteSupport(TestSessionType user_session_type) {
  switch (user_session_type) {
    case TestSessionType::kManuallyLaunchedWebKioskSession:
    case TestSessionType::kManuallyLaunchedKioskSession:
    case TestSessionType::kAutoLaunchedWebKioskSession:
    case TestSessionType::kAutoLaunchedKioskSession:
    case TestSessionType::kManagedGuestSession:
    case TestSessionType::kAffiliatedUserSession:
      return true;

    case TestSessionType::kGuestSession:
    case TestSessionType::kUnaffiliatedUserSession:
    case TestSessionType::kNoSession:
      return false;
  }
}

// Returns true if the given session type supports a 'remote access' session.
bool SupportsRemoteAccess(TestSessionType user_session_type) {
  switch (user_session_type) {
    case TestSessionType::kNoSession:
      return true;

    case TestSessionType::kManuallyLaunchedWebKioskSession:
    case TestSessionType::kManuallyLaunchedKioskSession:
    case TestSessionType::kAutoLaunchedWebKioskSession:
    case TestSessionType::kAutoLaunchedKioskSession:
    case TestSessionType::kManagedGuestSession:
    case TestSessionType::kAffiliatedUserSession:
    case TestSessionType::kGuestSession:
    case TestSessionType::kUnaffiliatedUserSession:
      return false;
  }
}

// Returns true if the given session type is a kiosk session.
bool IsKioskSession(TestSessionType user_session_type) {
  switch (user_session_type) {
    case TestSessionType::kManuallyLaunchedWebKioskSession:
    case TestSessionType::kManuallyLaunchedKioskSession:
    case TestSessionType::kAutoLaunchedWebKioskSession:
    case TestSessionType::kAutoLaunchedKioskSession:
      return true;
    case TestSessionType::kNoSession:
    case TestSessionType::kManagedGuestSession:
    case TestSessionType::kAffiliatedUserSession:
    case TestSessionType::kGuestSession:
    case TestSessionType::kUnaffiliatedUserSession:
      return false;
  }
}

struct Result {
  RemoteCommandJob::Status status;
  std::string payload;
};

class MockCrosNetworkConfig : public FakeCrosNetworkConfigBase {
 public:
  MockCrosNetworkConfig() = default;
  MockCrosNetworkConfig(const MockCrosNetworkConfig&) = delete;
  MockCrosNetworkConfig& operator=(const MockCrosNetworkConfig&) = delete;
  ~MockCrosNetworkConfig() override = default;

  MOCK_METHOD(void,
              GetNetworkStateList,
              (chromeos::network_config::mojom::NetworkFilterPtr filter,
               GetNetworkStateListCallback callback));
};

}  // namespace

class DeviceCommandStartCrdSessionJobTest : public ash::DeviceSettingsTestBase {
 public:
  DeviceCommandStartCrdSessionJobTest()
      : ash::DeviceSettingsTestBase(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  // ash::DeviceSettingsTestBase implementation:
  void SetUp() override {
    DeviceSettingsTestBase::SetUp();

    ASSERT_TRUE(profile_manager_.SetUp());

    user_activity_detector_ = ui::UserActivityDetector::Get();
    web_kiosk_app_manager_ = std::make_unique<ash::WebKioskAppManager>();
    kiosk_chrome_app_manager_ = std::make_unique<ash::KioskChromeAppManager>();
  }

  void TearDown() override {
    kiosk_chrome_app_manager_.reset();
    web_kiosk_app_manager_.reset();

    profile_ = nullptr;

    DeviceSettingsTestBase::TearDown();
  }

  Payload CreateSuccessPayload(const std::string& access_code);
  Payload CreateErrorPayload(StartCrdSessionResultCode result_code,
                             const std::string& error_message);
  Payload CreateNotIdlePayload(int idle_time_in_sec);

  void StartSessionOfType(TestSessionType user_session_type) {
    profile_ = StartSessionOfTypeWithProfile(user_session_type, user_manager(),
                                             profile_manager_);
  }

  void LogInAsKioskUser() {
    StartSessionOfType(TestSessionType::kAutoLaunchedWebKioskSession);
  }

  void LogInAsRegularUser() {
    StartSessionOfType(TestSessionType::kUnaffiliatedUserSession);
  }

  void LogInAsAffiliatedUser() {
    StartSessionOfType(TestSessionType::kAffiliatedUserSession);
  }

  void SetDeviceIdleTime(int idle_time_in_sec) {
    user_activity_detector_->set_last_activity_time_for_test(
        base::TimeTicks::Now() - base::Seconds(idle_time_in_sec));
  }

  void SetLastDeviceActivityTime(base::TimeTicks value) {
    user_activity_detector_->set_last_activity_time_for_test(value);
  }

  void SetRobotAccountUserName(std::string_view user_name) {
    robot_account_id_ = user_name;
  }

  FakeStartCrdSessionJobDelegate& delegate() { return delegate_; }

  DeviceCommandStartCrdSessionJob CreateJob() {
    return DeviceCommandStartCrdSessionJob{delegate_, robot_account_id_};
  }

  Result RunJobAndWaitForResult(const Payload& payload = Payload()) {
    DeviceCommandStartCrdSessionJob job{CreateJob()};

    bool initialized = InitializeJob(job, payload);
    if (!initialized) {
      ADD_FAILURE() << "Failed to initialize job";
      return Result{};
    }

    base::test::TestFuture<void> done_signal_;
    RunJob(job, done_signal_.GetCallback());
    EXPECT_TRUE(done_signal_.Wait());

    std::string response_payload =
        job.GetResultPayload() ? *job.GetResultPayload() : "{}";
    return Result{job.status(), response_payload};
  }

  bool InitializeJob(DeviceCommandStartCrdSessionJob& job,
                     const Payload& payload = Payload()) {
    bool success =
        job.Init(base::TimeTicks::Now(),
                 GenerateCommandProto(kUniqueID, base::TimeDelta(),
                                      base::WriteJson(payload).value()),
                 em::SignedData());

    if (success) {
      EXPECT_EQ(kUniqueID, job.unique_id());
      EXPECT_EQ(RemoteCommandJob::NOT_STARTED, job.status());
    }
    return success;
  }

  void SetKioskTroubleshootingPolicyValue(bool enabled) {
    ASSERT_TRUE(profile_);
    profile_->GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled,
                                     enabled);
  }

  void SetDeviceAllowEnterpriseRemoteAccessPolicyValue(bool enabled) {
    profile_manager_.local_state()->Get()->SetBoolean(
        prefs::kDeviceAllowEnterpriseRemoteAccessConnections, enabled);
  }

  void SetRemoteAccessHostAllowEnterpriseRemoteSupportConnections(
      bool enabled) {
    profile_manager_.local_state()->Get()->SetBoolean(
        prefs::kRemoteAccessHostAllowEnterpriseRemoteSupportConnections,
        enabled);
  }

  void RunJob(DeviceCommandStartCrdSessionJob& job,
              base::OnceClosure on_done_closure = base::OnceClosure()) {
    bool launched = job.Run(base::Time::Now(), base::TimeTicks::Now(),
                            std::move(on_done_closure));
    ASSERT_TRUE(launched);
    return;
  }

  test::FakeCrosNetworkConfig& fake_cros_network_config() {
    return fake_cros_network_config_;
  }

  ash::FakeChromeUserManager& user_manager() { return *user_manager_; }

 private:
  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
      user_manager_{std::make_unique<ash::FakeChromeUserManager>()};

  std::unique_ptr<ash::WebKioskAppManager> web_kiosk_app_manager_;
  std::unique_ptr<ash::KioskChromeAppManager> kiosk_chrome_app_manager_;

  // Parameters passed to the constructor of `DeviceCommandStartCrdSessionJob`
  // when the job is created.
  std::string robot_account_id_ = "[email protected]";

  raw_ptr<ui::UserActivityDetector> user_activity_detector_;

  FakeStartCrdSessionJobDelegate delegate_;

  test::ScopedFakeCrosNetworkConfig fake_cros_network_config_;

  TestingProfileManager profile_manager_{TestingBrowserProcess::GetGlobal()};
  raw_ptr<TestingProfile> profile_ = nullptr;
};

// Fixture for tests parameterized over the possible session types
// (`TestSessionType`).
class DeviceCommandStartCrdSessionJobTestParameterized
    : public DeviceCommandStartCrdSessionJobTest,
      public ::testing::WithParamInterface<test::TestSessionType> {};

// Fixture for tests parameterized over boolean values.
class DeviceCommandStartCrdSessionJobTestBoolParameterized
    : public DeviceCommandStartCrdSessionJobTest,
      public ::testing::WithParamInterface<bool> {};

Payload DeviceCommandStartCrdSessionJobTest::CreateSuccessPayload(
    const std::string& access_code) {
  return Payload()
      .Set(kResultCodeFieldName,
           static_cast<int>(
               StartCrdSessionResultCode::START_CRD_SESSION_SUCCESS))
      .Set(kResultAccessCodeFieldName, access_code);
}

Payload DeviceCommandStartCrdSessionJobTest::CreateErrorPayload(
    StartCrdSessionResultCode result_code,
    const std::string& error_message = "") {
  auto payload = Payload()  //
                     .Set(kResultCodeFieldName, static_cast<int>(result_code));
  if (!error_message.empty()) {
    payload.Set(kResultMessageFieldName, error_message);
  }
  return payload;
}

Payload DeviceCommandStartCrdSessionJobTest::CreateNotIdlePayload(
    int idle_time_in_sec) {
  return Payload()
      .Set(kResultCodeFieldName,
           static_cast<int>(StartCrdSessionResultCode::FAILURE_NOT_IDLE))
      .Set(kResultLastActivityFieldName, idle_time_in_sec);
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldTerminateActiveSessionAndThenSucceed) {
  LogInAsKioskUser();

  delegate().SetHasActiveSession(true);

  EXPECT_SUCCESS(RunJobAndWaitForResult());
  EXPECT_TRUE(delegate().IsActiveSessionTerminated());
}

TEST_P(DeviceCommandStartCrdSessionJobTestParameterized,
       TestRemoteSupportSessions) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));

  StartSessionOfType(user_session_type);
  Result result = RunJobAndWaitForResult();

  bool is_supported = [&]() {
    switch (user_session_type) {
      case TestSessionType::kManuallyLaunchedWebKioskSession:
      case TestSessionType::kManuallyLaunchedKioskSession:
      case TestSessionType::kAutoLaunchedWebKioskSession:
      case TestSessionType::kAutoLaunchedKioskSession:
      case TestSessionType::kManagedGuestSession:
      case TestSessionType::kAffiliatedUserSession:
        return true;

      case TestSessionType::kGuestSession:
      case TestSessionType::kUnaffiliatedUserSession:
      case TestSessionType::kNoSession:
        return false;
    }
  }();

  if (is_supported) {
    EXPECT_SUCCESS(result);
  } else {
    EXPECT_ERROR(result,
                 StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
  }
}

TEST_P(DeviceCommandStartCrdSessionJobTestParameterized,
       RemoteSupportSessionAvailabilityShouldBeUnaffectedByRemoteAccessPolicy) {
  SetDeviceAllowEnterpriseRemoteAccessPolicyValue(false);
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));

  StartSessionOfType(user_session_type);
  Result result = RunJobAndWaitForResult();

  bool is_supported = [&]() {
    switch (user_session_type) {
      case TestSessionType::kManuallyLaunchedWebKioskSession:
      case TestSessionType::kManuallyLaunchedKioskSession:
      case TestSessionType::kAutoLaunchedWebKioskSession:
      case TestSessionType::kAutoLaunchedKioskSession:
      case TestSessionType::kManagedGuestSession:
      case TestSessionType::kAffiliatedUserSession:
        return true;

      case TestSessionType::kGuestSession:
      case TestSessionType::kUnaffiliatedUserSession:
      case TestSessionType::kNoSession:
        return false;
    }
  }();

  if (is_supported) {
    EXPECT_SUCCESS(result);
  } else {
    EXPECT_ERROR(result,
                 StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
  }
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldFailIfDeviceIdleTimeIsLessThanIdlenessCutoffValue) {
  LogInAsKioskUser();

  const int device_idle_time_in_sec = 9;
  const int idleness_cutoff_in_sec = 10;

  SetDeviceIdleTime(device_idle_time_in_sec);

  Result result = RunJobAndWaitForResult(
      Payload().Set("idlenessCutoffSec", idleness_cutoff_in_sec));
  EXPECT_EQ(result.status, RemoteCommandJob::Status::FAILED);
  EXPECT_THAT(result.payload,
              IsJson(CreateNotIdlePayload(device_idle_time_in_sec)));
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldSucceedIfDeviceIdleTimeIsMoreThanIdlenessCutoffValue) {
  LogInAsKioskUser();

  const int device_idle_time_in_sec = 10;
  const int idleness_cutoff_in_sec = 9;

  SetDeviceIdleTime(device_idle_time_in_sec);

  EXPECT_SUCCESS(RunJobAndWaitForResult(
      Payload().Set("idlenessCutoffSec", idleness_cutoff_in_sec)));
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldSucceedIfThereWasNeverActivityOnTheDevice) {
  LogInAsKioskUser();

  base::TimeTicks never;
  ASSERT_TRUE(never.is_null());
  SetLastDeviceActivityTime(never);

  EXPECT_SUCCESS(
      RunJobAndWaitForResult(Payload().Set("idlenessCutoffSec", 100000000)));
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldCheckUserTypeBeforeDeviceIdleTime) {
  // If we were to check device idle time first, the remote admin would
  // still be asked to acknowledge the user's presence, even if they are not
  // allowed to start a CRD connection anyway.
  LogInAsRegularUser();

  const int device_idle_time_in_sec = 9;
  const int idleness_cutoff_in_sec = 10;

  SetDeviceIdleTime(device_idle_time_in_sec);

  EXPECT_ERROR(RunJobAndWaitForResult(
                   Payload().Set("idlenessCutoffSec", idleness_cutoff_in_sec)),
               StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
}

TEST_F(DeviceCommandStartCrdSessionJobTest, ShouldFailIfCrdHostReportsAnError) {
  LogInAsKioskUser();

  delegate().FailWithError(
      ExtendedStartCrdSessionResultCode::kFailureCrdHostError);

  EXPECT_ERROR(RunJobAndWaitForResult(),
               StartCrdSessionResultCode::FAILURE_CRD_HOST_ERROR);
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldPassRobotAccountNameToDelegate) {
  LogInAsKioskUser();

  SetRobotAccountUserName("[email protected]");

  EXPECT_SUCCESS(RunJobAndWaitForResult());

  EXPECT_EQ("[email protected]",
            delegate().session_parameters().user_name);
}

TEST_F(DeviceCommandStartCrdSessionJobTest, ShouldPassAdminEmailToDelegate) {
  LogInAsKioskUser();

  EXPECT_SUCCESS(
      RunJobAndWaitForResult(Payload().Set("adminEmail", "[email protected]")));

  EXPECT_EQ("[email protected]", delegate().session_parameters().admin_email);
}

TEST_P(DeviceCommandStartCrdSessionJobTestBoolParameterized,
       ShouldPassAllowTroubleshootingToolsToDelegateForKiosk) {
  LogInAsKioskUser();

  SetKioskTroubleshootingPolicyValue(GetParam());
  EXPECT_SUCCESS(RunJobAndWaitForResult());

  EXPECT_EQ(GetParam(),
            delegate().session_parameters().allow_troubleshooting_tools);
}

TEST_P(DeviceCommandStartCrdSessionJobTestBoolParameterized,
       ShouldNotPassAllowTroubleshootingToolsToDelegateForUser) {
  LogInAsAffiliatedUser();

  SetKioskTroubleshootingPolicyValue(GetParam());
  EXPECT_SUCCESS(RunJobAndWaitForResult());

  EXPECT_FALSE(delegate().session_parameters().allow_troubleshooting_tools);
}

TEST_P(DeviceCommandStartCrdSessionJobTestBoolParameterized,
       ShouldPassShowTroubleshootingToolsToDelegateForKiosk) {
  LogInAsKioskUser();

  SetKioskTroubleshootingPolicyValue(GetParam());
  EXPECT_SUCCESS(RunJobAndWaitForResult());

  // Troubleshooting tools are always shown in the client UI for kiosk sessions.
  EXPECT_TRUE(delegate().session_parameters().show_troubleshooting_tools);
}

TEST_P(DeviceCommandStartCrdSessionJobTestBoolParameterized,
       ShouldNotPassShowTroubleshootingToolsToDelegateForUser) {
  LogInAsAffiliatedUser();

  SetKioskTroubleshootingPolicyValue(GetParam());
  EXPECT_SUCCESS(RunJobAndWaitForResult());

  // Troubleshooting tools are never shown in the UI for non-kiosk sessions.
  EXPECT_FALSE(delegate().session_parameters().show_troubleshooting_tools);
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldNotSetAdminEmailWhenNotSpecifiedInPayload) {
  LogInAsKioskUser();

  EXPECT_SUCCESS(RunJobAndWaitForResult(Payload()));

  EXPECT_EQ(std::nullopt, delegate().session_parameters().admin_email);
}

TEST_P(DeviceCommandStartCrdSessionJobTestParameterized,
       TestTerminateUponInputForRemoteSupportWithAckedUserPresenceFalse) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));

  if (!SupportsRemoteSupport(user_session_type)) {
    return;
  }

  StartSessionOfType(user_session_type);
  Result result =
      RunJobAndWaitForResult(Payload().Set("ackedUserPresence", false));

  bool terminate_upon_input = [&]() {
    switch (user_session_type) {
      case TestSessionType::kManuallyLaunchedWebKioskSession:
      case TestSessionType::kManuallyLaunchedKioskSession:
      case TestSessionType::kAutoLaunchedWebKioskSession:
      case TestSessionType::kAutoLaunchedKioskSession:
        return true;

      case TestSessionType::kManagedGuestSession:
      case TestSessionType::kAffiliatedUserSession:
        return false;

      case TestSessionType::kGuestSession:
      case TestSessionType::kUnaffiliatedUserSession:
      case TestSessionType::kNoSession:
        // Unsupported session types
        NOTREACHED_IN_MIGRATION();
        return false;
    }
  }();

  EXPECT_SUCCESS(result);
  EXPECT_EQ(terminate_upon_input,
            delegate().session_parameters().terminate_upon_input);
}

TEST_P(DeviceCommandStartCrdSessionJobTestParameterized,
       TestTerminateUponInputForRemoteSupportWithAckedUserPresenceTrue) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));

  if (!SupportsRemoteSupport(user_session_type)) {
    return;
  }

  StartSessionOfType(user_session_type);
  Result result =
      RunJobAndWaitForResult(Payload().Set("ackedUserPresence", true));

  // If the user presence is acknowledged we never need to terminate upon user
  // input.
  const bool terminate_upon_input = false;

  EXPECT_SUCCESS(result);
  EXPECT_EQ(terminate_upon_input,
            delegate().session_parameters().terminate_upon_input);
}

TEST_P(DeviceCommandStartCrdSessionJobTestParameterized,
       TestShowConfirmationDialogForRemoteSupport) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));

  if (!SupportsRemoteSupport(user_session_type)) {
    return;
  }

  StartSessionOfType(user_session_type);
  Result result =
      RunJobAndWaitForResult(Payload().Set("ackedUserPresence", true));

  bool show_confirmation_dialog = [&]() {
    switch (user_session_type) {
      case TestSessionType::kManuallyLaunchedWebKioskSession:
      case TestSessionType::kManuallyLaunchedKioskSession:
      case TestSessionType::kAutoLaunchedWebKioskSession:
      case TestSessionType::kAutoLaunchedKioskSession:
        return false;

      case TestSessionType::kManagedGuestSession:
      case TestSessionType::kAffiliatedUserSession:
        return true;

      case TestSessionType::kGuestSession:
      case TestSessionType::kUnaffiliatedUserSession:
      case TestSessionType::kNoSession:
        // Unsupported session types
        NOTREACHED_IN_MIGRATION();
        return false;
    }
  }();

  EXPECT_SUCCESS(result);
  EXPECT_EQ(show_confirmation_dialog,
            delegate().session_parameters().show_confirmation_dialog);
}

TEST_P(DeviceCommandStartCrdSessionJobTestParameterized,
       ShouldSendSessionDurationLogForRemoteSupport) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  base::TimeDelta duration = base::Seconds(1);

  if (!SupportsRemoteSupport(user_session_type)) {
    return;
  }
  base::HistogramTester histogram_tester;
  StartSessionOfType(user_session_type);
  RunJobAndWaitForResult();
  delegate().TerminateCrdSession(duration);

  histogram_tester.ExpectUniqueTimeSample(
      base::StringPrintf(kHistogramDurationTemplate, "RemoteSupport",
                         SessionTypeToUmaString(user_session_type)),
      duration, /*expected_bucket_count=*/1);
}

TEST_P(DeviceCommandStartCrdSessionJobTestParameterized,
       ShouldAllowFileTransferForKioskSessionsWhenFeatureIsEnabled) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  if (!SupportsRemoteSupport(user_session_type)) {
    return;
  }
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kEnableCrdFileTransferForKiosk);
  StartSessionOfType(user_session_type);
  RunJobAndWaitForResult();
  bool supports_file_transfer = IsKioskSession(user_session_type);

  EXPECT_EQ(delegate().session_parameters().allow_file_transfer,
            supports_file_transfer);
}

TEST_P(DeviceCommandStartCrdSessionJobTestParameterized,
       ShouldNotAllowFileTransferForAnySessionWhenFeatureIsNotEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(kEnableCrdFileTransferForKiosk);

  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  if (!SupportsRemoteSupport(user_session_type)) {
    return;
  }

  StartSessionOfType(user_session_type);
  RunJobAndWaitForResult();

  EXPECT_EQ(delegate().session_parameters().allow_file_transfer, false);
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldSendErrorUmaLogWhenUserTypeIsNotSupported) {
  base::HistogramTester histogram_tester;

  LogInAsRegularUser();
  RunJobAndWaitForResult();

  histogram_tester.ExpectUniqueSample(
      "Enterprise.DeviceRemoteCommand.Crd.Result",
      ExtendedStartCrdSessionResultCode::kFailureUnsupportedUserType, 1);
  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kHistogramResultTemplate, "RemoteSupport",
                         "UnaffiliatedUserSession"),
      ExtendedStartCrdSessionResultCode::kFailureUnsupportedUserType,
      /*expected_bucket_count=*/1);
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldSendErrorUmaLogWhenDeviceIsNotIdle) {
  base::HistogramTester histogram_tester;
  LogInAsKioskUser();

  const int device_idle_time_in_sec = 9;
  const int idleness_cutoff_in_sec = 10;

  SetDeviceIdleTime(device_idle_time_in_sec);
  RunJobAndWaitForResult(
      Payload().Set("idlenessCutoffSec", idleness_cutoff_in_sec));

  histogram_tester.ExpectUniqueSample(
      "Enterprise.DeviceRemoteCommand.Crd.Result",
      ExtendedStartCrdSessionResultCode::kFailureNotIdle, 1);
  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kHistogramResultTemplate, "RemoteSupport",
                         "AutoLaunchedKioskSession"),
      ExtendedStartCrdSessionResultCode::kFailureNotIdle,
      /*expected_bucket_count=*/1);
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldSendErrorUmaLogFailureNoAuthToken) {
  base::HistogramTester histogram_tester;
  LogInAsKioskUser();

  delegate().FailWithError(
      ExtendedStartCrdSessionResultCode::kFailureNoOauthToken);
  RunJobAndWaitForResult();

  histogram_tester.ExpectUniqueSample(
      "Enterprise.DeviceRemoteCommand.Crd.Result",
      ExtendedStartCrdSessionResultCode::kFailureNoOauthToken, 1);
  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kHistogramResultTemplate, "RemoteSupport",
                         "AutoLaunchedKioskSession"),
      ExtendedStartCrdSessionResultCode::kFailureNoOauthToken,
      /*expected_bucket_count=*/1);
}

TEST_F(DeviceCommandStartCrdSessionJobTest,
       ShouldSendErrorUmaLogFailureCrdHostError) {
  base::HistogramTester histogram_tester;
  LogInAsKioskUser();

  delegate().FailWithError(
      ExtendedStartCrdSessionResultCode::kFailureCrdHostError);

  RunJobAndWaitForResult();

  histogram_tester.ExpectUniqueSample(
      "Enterprise.DeviceRemoteCommand.Crd.Result",
      ExtendedStartCrdSessionResultCode::kFailureCrdHostError, 1);
  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kHistogramResultTemplate, "RemoteSupport",
                         "AutoLaunchedKioskSession"),
      ExtendedStartCrdSessionResultCode::kFailureCrdHostError,
      /*expected_bucket_count=*/1);
}

class DeviceCommandStartCrdSessionJobRemoteAccessTest
    : public DeviceCommandStartCrdSessionJobTest {
 public:
  void SetUp() override {
    EnableFeature(kEnableCrdAdminRemoteAccess);
    DeviceCommandStartCrdSessionJobTest::SetUp();
  }

  void EnableFeature(const base::Feature& feature) {
    feature_.Reset();
    feature_.InitAndEnableFeature(feature);
  }

  void DisableFeature(const base::Feature& feature) {
    feature_.Reset();
    feature_.InitAndDisableFeature(feature);
  }

  // Return a `RemoteCommand` payload that would start a remote access session.
  Payload RemoteAccessPayload() {
    return Payload().Set("crdSessionType",
                         CrdSessionType::REMOTE_ACCESS_SESSION);
  }

  void AddActiveManagedNetwork() {
    fake_cros_network_config().AddActiveNetwork(
        CreateNetwork(NetworkType::kWiFi)
            .SetOncSource(OncSource::kDevicePolicy));
  }

 private:
  base::test::ScopedFeatureList feature_;
};

// Fixture for tests parameterized over the possible session types
// (`TestSessionType`).
class DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized
    : public DeviceCommandStartCrdSessionJobRemoteAccessTest,
      public ::testing::WithParamInterface<test::TestSessionType> {};

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldUseRemoteSupportIfCrdSessionTypeIsUnspecified) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  StartSessionOfType(user_session_type);

  auto payload_without_crd_session_type = Payload();

  Result result =
      RunJobAndWaitForResult(std::move(payload_without_crd_session_type));

  if (SupportsRemoteSupport(user_session_type)) {
    EXPECT_SUCCESS(result);
    // Ensure the session a remote support session (= not curtained off).
    EXPECT_FALSE(delegate().session_parameters().curtain_local_user_session);
  } else {
    EXPECT_ERROR(result,
                 StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
  }
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldUseRemoteSupportIfRequestedInPayload) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  StartSessionOfType(user_session_type);

  Result result = RunJobAndWaitForResult(
      Payload().Set("crdSessionType", CrdSessionType::REMOTE_SUPPORT_SESSION));

  if (SupportsRemoteSupport(user_session_type)) {
    EXPECT_SUCCESS(result);
    // Ensure the session a remote support session (= not curtained off).
    EXPECT_FALSE(delegate().session_parameters().curtain_local_user_session);
  } else {
    EXPECT_ERROR(result,
                 StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
  }
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldUseRemoteAccessIfRequestedInPayload) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  StartSessionOfType(user_session_type);
  AddActiveManagedNetwork();

  Result result = RunJobAndWaitForResult(
      Payload().Set("crdSessionType", CrdSessionType::REMOTE_ACCESS_SESSION));

  if (SupportsRemoteAccess(user_session_type)) {
    EXPECT_SUCCESS(result);
    // Ensure the session a remote access session (= curtained off).
    EXPECT_TRUE(delegate().session_parameters().curtain_local_user_session);
  } else {
    EXPECT_ERROR(result,
                 StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
  }
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldAllowRemoteAccessConnectionsWhenPolicyIsNotSet) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  StartSessionOfType(user_session_type);
  AddActiveManagedNetwork();

  Result result = RunJobAndWaitForResult(
      Payload().Set("crdSessionType", CrdSessionType::REMOTE_ACCESS_SESSION));

  if (SupportsRemoteAccess(user_session_type)) {
    EXPECT_SUCCESS(result);
    // Ensure the session a remote access session (= curtained off).
    EXPECT_TRUE(delegate().session_parameters().curtain_local_user_session);
  } else {
    EXPECT_ERROR(result,
                 StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
  }
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldAllowRemoteAccessConnectionsWhenPolicyIsEnabled) {
  SetDeviceAllowEnterpriseRemoteAccessPolicyValue(true);
  SetRemoteAccessHostAllowEnterpriseRemoteSupportConnections(true);
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  StartSessionOfType(user_session_type);
  AddActiveManagedNetwork();

  Result result = RunJobAndWaitForResult(
      Payload().Set("crdSessionType", CrdSessionType::REMOTE_ACCESS_SESSION));

  if (SupportsRemoteAccess(user_session_type)) {
    EXPECT_SUCCESS(result);
    // Ensure the session a remote access session (= curtained off).
    EXPECT_TRUE(delegate().session_parameters().curtain_local_user_session);
  } else {
    EXPECT_ERROR(result,
                 StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
  }
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldNotAllowRemoteAccessConnectionsWhenDevicePolicyIsDisabled) {
  SetDeviceAllowEnterpriseRemoteAccessPolicyValue(false);
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  StartSessionOfType(user_session_type);
  AddActiveManagedNetwork();

  Result result = RunJobAndWaitForResult(
      Payload().Set("crdSessionType", CrdSessionType::REMOTE_ACCESS_SESSION));

  if (SupportsRemoteAccess(user_session_type)) {
    EXPECT_ERROR(result, StartCrdSessionResultCode::FAILURE_DISABLED_BY_POLICY);
  } else {
    EXPECT_ERROR(result,
                 StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
  }
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldNotAllowRemoteAccessConnectionsWhenRemoteSupportPolicyIsDisabled) {
  SetRemoteAccessHostAllowEnterpriseRemoteSupportConnections(false);
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  StartSessionOfType(user_session_type);
  AddActiveManagedNetwork();

  Result result = RunJobAndWaitForResult(
      Payload().Set("crdSessionType", CrdSessionType::REMOTE_ACCESS_SESSION));

  if (SupportsRemoteAccess(user_session_type)) {
    EXPECT_ERROR(result, StartCrdSessionResultCode::FAILURE_DISABLED_BY_POLICY);
  } else {
    EXPECT_ERROR(result,
                 StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
  }
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldUseRemoteSupportIfFeatureIsDisabled) {
  DisableFeature(kEnableCrdAdminRemoteAccess);

  LogInAsKioskUser();

  EXPECT_SUCCESS(RunJobAndWaitForResult());
  EXPECT_FALSE(delegate().session_parameters().curtain_local_user_session);
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldRejectCrdSessionTypeInPayloadIfFeatureIsDisabled) {
  DisableFeature(kEnableCrdAdminRemoteAccess);

  DeviceCommandStartCrdSessionJob job{CreateJob()};
  bool success = InitializeJob(
      job,
      Payload().Set("crdSessionType", CrdSessionType::REMOTE_ACCESS_SESSION));

  EXPECT_FALSE(success);
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       RemoteAccessShouldFailForUnsupportedSessionTypes) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));

  bool is_supported = [&]() {
    switch (user_session_type) {
      case TestSessionType::kNoSession:
        return true;

      case TestSessionType::kManuallyLaunchedWebKioskSession:
      case TestSessionType::kManuallyLaunchedKioskSession:
      case TestSessionType::kAutoLaunchedWebKioskSession:
      case TestSessionType::kAutoLaunchedKioskSession:
      case TestSessionType::kManagedGuestSession:
      case TestSessionType::kAffiliatedUserSession:
      case TestSessionType::kGuestSession:
      case TestSessionType::kUnaffiliatedUserSession:
        return false;
    }
  }();

  if (is_supported) {
    // This test is only about the cases where remote access is not supported.
    return;
  }

  StartSessionOfType(user_session_type);
  Result result = RunJobAndWaitForResult(RemoteAccessPayload());

  EXPECT_ERROR(result,
               StartCrdSessionResultCode::FAILURE_UNSUPPORTED_USER_TYPE);
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldAllowReconnectionsForRemoteAccessSessionsIfV2FeatureIsEnabled) {
  TestSessionType user_session_type = GetParam();
  if (SupportsRemoteAccess(user_session_type)) {
    EnableFeature(kEnableCrdAdminRemoteAccessV2);

    SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                    SessionTypeToString(user_session_type)));
    StartSessionOfType(user_session_type);
    AddActiveManagedNetwork();

    Result result = RunJobAndWaitForResult(
        Payload().Set("crdSessionType", CrdSessionType::REMOTE_ACCESS_SESSION));

    EXPECT_SUCCESS(result);
    EXPECT_TRUE(delegate().session_parameters().allow_reconnections);
  }
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldNeverAllowFileTransferForRemoteAccessWhenFeatureIsEnabled) {
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  if (!SupportsRemoteAccess(user_session_type)) {
    return;
  }

  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kEnableCrdFileTransferForKiosk);
  StartSessionOfType(user_session_type);
  AddActiveManagedNetwork();
  RunJobAndWaitForResult(
      Payload().Set("crdSessionType", CrdSessionType::REMOTE_ACCESS_SESSION));

  EXPECT_EQ(delegate().session_parameters().allow_file_transfer, false);
}

TEST_P(
    DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
    ShouldNotAllowReconnectionsForRemoteAccessSessionsIfV2FeatureIsDisabled) {
  TestSessionType user_session_type = GetParam();
  if (SupportsRemoteAccess(user_session_type)) {
    DisableFeature(kEnableCrdAdminRemoteAccessV2);

    SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                    SessionTypeToString(user_session_type)));
    StartSessionOfType(user_session_type);
    AddActiveManagedNetwork();

    Result result = RunJobAndWaitForResult(
        Payload().Set("crdSessionType", CrdSessionType::REMOTE_ACCESS_SESSION));

    EXPECT_SUCCESS(result);
    EXPECT_FALSE(delegate().session_parameters().allow_reconnections);
  }
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldNeverAllowReconnectionsForRemoteSupport) {
  TestSessionType user_session_type = GetParam();
  if (SupportsRemoteSupport(user_session_type)) {
    EnableFeature(kEnableCrdAdminRemoteAccessV2);

    SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                    SessionTypeToString(user_session_type)));
    StartSessionOfType(user_session_type);

    Result result = RunJobAndWaitForResult(Payload().Set(
        "crdSessionType", CrdSessionType::REMOTE_SUPPORT_SESSION));

    EXPECT_SUCCESS(result);
    EXPECT_FALSE(delegate().session_parameters().allow_reconnections);
  }
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldSucceedIfNoUserIsLoggedIn) {
  AddActiveManagedNetwork();

  EXPECT_SUCCESS(RunJobAndWaitForResult(RemoteAccessPayload()));
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldNotTerminateUponInput) {
  AddActiveManagedNetwork();

  EXPECT_SUCCESS(RunJobAndWaitForResult(
      // This would enable terminate upon input in a Remote Support job.
      RemoteAccessPayload().Set("ackedUserPresense", false)));
  EXPECT_FALSE(delegate().session_parameters().terminate_upon_input);
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldNotShowConfirmationDialog) {
  AddActiveManagedNetwork();

  EXPECT_SUCCESS(RunJobAndWaitForResult(RemoteAccessPayload()));
  EXPECT_FALSE(delegate().session_parameters().show_confirmation_dialog);
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldRejectRequestIfThereAreNoActiveNetworks) {
  fake_cros_network_config().ClearActiveNetworks();

  EXPECT_ERROR(RunJobAndWaitForResult(RemoteAccessPayload()),
               StartCrdSessionResultCode::FAILURE_UNMANAGED_ENVIRONMENT);
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldRejectRequestIfOnlyUnmanagedNetworksAreAvailable) {
  fake_cros_network_config().SetActiveNetworks({
      CreateNetwork(NetworkType::kWiFi).SetOncSource(OncSource::kNone),
      CreateNetwork(NetworkType::kEthernet).SetOncSource(OncSource::kNone),
      CreateNetwork(NetworkType::kTether).SetOncSource(OncSource::kNone),
      CreateNetwork(NetworkType::kVPN).SetOncSource(OncSource::kNone),
  });

  EXPECT_ERROR(RunJobAndWaitForResult(RemoteAccessPayload()),
               StartCrdSessionResultCode::FAILURE_UNMANAGED_ENVIRONMENT);
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldRejectRequestIfTheOnlyManagedNetworkIsCellular) {
  fake_cros_network_config().SetActiveNetworks({
      CreateNetwork(NetworkType::kCellular)
          .SetOncSource(OncSource::kDevicePolicy),
  });

  EXPECT_ERROR(RunJobAndWaitForResult(RemoteAccessPayload()),
               StartCrdSessionResultCode::FAILURE_UNMANAGED_ENVIRONMENT);
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldAllowRequestIfManagedWifiNetworkIsAvailable) {
  fake_cros_network_config().SetActiveNetworks({
      CreateNetwork(NetworkType::kWiFi).SetOncSource(OncSource::kDevicePolicy),
  });

  EXPECT_SUCCESS(RunJobAndWaitForResult(RemoteAccessPayload()));
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldAllowRequestIfManagedEthernetNetworkIsAvailable) {
  fake_cros_network_config().SetActiveNetworks({
      CreateNetwork(NetworkType::kEthernet)
          .SetOncSource(OncSource::kDevicePolicy),
  });

  EXPECT_SUCCESS(RunJobAndWaitForResult(RemoteAccessPayload()));
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldAllowRequestIfManagedTetherNetworkIsAvailable) {
  fake_cros_network_config().SetActiveNetworks({
      CreateNetwork(NetworkType::kTether)
          .SetOncSource(OncSource::kDevicePolicy),
  });

  EXPECT_SUCCESS(RunJobAndWaitForResult(RemoteAccessPayload()));
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldRejectRequestIfManagedNetworkIsVpn) {
  fake_cros_network_config().SetActiveNetworks({
      CreateNetwork(NetworkType::kVPN).SetOncSource(OncSource::kDevicePolicy),
  });

  EXPECT_ERROR(RunJobAndWaitForResult(RemoteAccessPayload()),
               StartCrdSessionResultCode::FAILURE_UNMANAGED_ENVIRONMENT);
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldNotOnlyLookAtFirstNetwork) {
  fake_cros_network_config().SetActiveNetworks({
      CreateNetwork().SetOncSource(OncSource::kNone),
      CreateNetwork().SetOncSource(OncSource::kDevicePolicy),
      CreateNetwork().SetOncSource(OncSource::kNone),
  });

  EXPECT_SUCCESS(RunJobAndWaitForResult(RemoteAccessPayload()));
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldOnlyAllowPolicyOncSources) {
  for (auto [source, is_allowed] : {
           std::make_pair(OncSource::kNone, /*is_allowed=*/false),
           std::make_pair(OncSource::kDevice, /*is_allowed=*/false),
           std::make_pair(OncSource::kUser, /*is_allowed=*/false),
           std::make_pair(OncSource::kDevicePolicy, /*is_allowed=*/true),
           std::make_pair(OncSource::kUserPolicy, /*is_allowed=*/true),
       }) {
    fake_cros_network_config().SetActiveNetworks({
        CreateNetwork().SetOncSource(source),
    });

    auto expected_result = is_allowed ? RemoteCommandJob::Status::SUCCEEDED
                                      : RemoteCommandJob::Status::FAILED;
    auto actual_result = RunJobAndWaitForResult(RemoteAccessPayload()).status;
    EXPECT_EQ(actual_result, expected_result);
  }
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldOnlyFetchTheActiveNetworks) {
  MockCrosNetworkConfig network_config_mock;
  ash::network_config::OverrideInProcessInstanceForTesting(
      &network_config_mock);

  TestFuture<chromeos::network_config::mojom::NetworkFilterPtr,
             MockCrosNetworkConfig::GetNetworkStateListCallback>
      get_network_state_future;
  EXPECT_CALL(network_config_mock, GetNetworkStateList)
      .WillOnce([&](auto filter, auto callback) {
        get_network_state_future.SetValue(std::move(filter),
                                          std::move(callback));
      });

  DeviceCommandStartCrdSessionJob job{CreateJob()};
  InitializeJob(job, RemoteAccessPayload());
  RunJob(job);

  auto [filter, callback] = get_network_state_future.Take();
  EXPECT_EQ(filter->filter,
            chromeos::network_config::mojom::FilterType::kActive);
  EXPECT_EQ(filter->network_type, NetworkType::kAll);
  EXPECT_EQ(filter->limit, chromeos::network_config::mojom::kNoLimit);

  // We must invoke the callback to satisfy the Mojom contract
  std::move(callback).Run({});
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldSendUmaLogsForRemoteAccessForUnsupportedUserType) {
  base::HistogramTester histogram_tester;
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));

  if (SupportsRemoteAccess(user_session_type)) {
    // This test is only about the cases where remote access is not supported.
    return;
  }
  AddActiveManagedNetwork();
  StartSessionOfType(user_session_type);
  Result result = RunJobAndWaitForResult(RemoteAccessPayload());

  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kHistogramResultTemplate, "RemoteAccess",
                         SessionTypeToUmaString(user_session_type)),
      ExtendedStartCrdSessionResultCode::kFailureUnsupportedUserType,
      /*expected_bucket_count=*/1);
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldSendUmaLogsForRemoteAccessForSupportedUserType) {
  base::HistogramTester histogram_tester;
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));

  if (!SupportsRemoteAccess(user_session_type)) {
    // This test is only about the cases where remote access is supported.
    return;
  }
  AddActiveManagedNetwork();
  StartSessionOfType(user_session_type);
  Result result = RunJobAndWaitForResult(RemoteAccessPayload());

  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kHistogramResultTemplate, "RemoteAccess",
                         SessionTypeToUmaString(user_session_type)),
      ExtendedStartCrdSessionResultCode::kSuccess, /*expected_bucket_count=*/1);
}

TEST_F(DeviceCommandStartCrdSessionJobRemoteAccessTest,
       ShouldSendUmaLogsIfThereAreNoActiveNetworks) {
  fake_cros_network_config().ClearActiveNetworks();
  base::HistogramTester histogram_tester;

  RunJobAndWaitForResult(RemoteAccessPayload());

  histogram_tester.ExpectUniqueSample(
      base::StringPrintf(kHistogramResultTemplate, "RemoteAccess",
                         SessionTypeToUmaString(TestSessionType::kNoSession)),
      ExtendedStartCrdSessionResultCode::kFailureUnmanagedEnvironment,
      /*expected_bucket_count=*/1);
}

TEST_P(DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
       ShouldSendSessionDurationUmaLogWhenCrdSessionFinish) {
  base::HistogramTester histogram_tester;
  TestSessionType user_session_type = GetParam();
  SCOPED_TRACE(base::StringPrintf("Testing session type %s",
                                  SessionTypeToString(user_session_type)));
  base::TimeDelta duration = base::Seconds(1);

  if (!SupportsRemoteAccess(user_session_type)) {
    // This test is only about the cases where remote access is supported.
    return;
  }
  AddActiveManagedNetwork();
  StartSessionOfType(user_session_type);
  Result result = RunJobAndWaitForResult(RemoteAccessPayload());
  delegate().TerminateCrdSession(duration);

  histogram_tester.ExpectUniqueTimeSample(
      base::StringPrintf(kHistogramDurationTemplate, "RemoteAccess",
                         SessionTypeToUmaString(user_session_type)),
      duration, /*expected_bucket_count=*/1);
}

INSTANTIATE_TEST_SUITE_P(
    All,
    DeviceCommandStartCrdSessionJobTestParameterized,
    ::testing::Values(TestSessionType::kManuallyLaunchedWebKioskSession,
                      TestSessionType::kManuallyLaunchedKioskSession,
                      TestSessionType::kAutoLaunchedWebKioskSession,
                      TestSessionType::kAutoLaunchedKioskSession,
                      TestSessionType::kManagedGuestSession,
                      TestSessionType::kGuestSession,
                      TestSessionType::kAffiliatedUserSession,
                      TestSessionType::kUnaffiliatedUserSession,
                      TestSessionType::kNoSession));

INSTANTIATE_TEST_SUITE_P(
    All,
    DeviceCommandStartCrdSessionJobRemoteAccessTestParameterized,
    ::testing::Values(TestSessionType::kManuallyLaunchedWebKioskSession,
                      TestSessionType::kManuallyLaunchedKioskSession,
                      TestSessionType::kAutoLaunchedWebKioskSession,
                      TestSessionType::kAutoLaunchedKioskSession,
                      TestSessionType::kManagedGuestSession,
                      TestSessionType::kGuestSession,
                      TestSessionType::kAffiliatedUserSession,
                      TestSessionType::kUnaffiliatedUserSession,
                      TestSessionType::kNoSession));
INSTANTIATE_TEST_SUITE_P(All,
                         DeviceCommandStartCrdSessionJobTestBoolParameterized,
                         testing::Bool());

}  // namespace policy