// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/session_policies_from_dict.h"

#include <optional>

#include "base/no_destructor.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/policy/policy_constants.h"
#include "remoting/base/session_policies.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace remoting {

namespace {

const SessionPolicies GetFullSessionPolicies() {
  SessionPolicies session_policies;
  session_policies.clipboard_size_bytes = 1024;
  session_policies.allow_stun_connections = true;
  session_policies.allow_relayed_connections = false;
  session_policies.host_udp_port_range = *PortRange::Create(123, 456);
#if !BUILDFLAG(IS_CHROMEOS)
  session_policies.allow_file_transfer = true;
  session_policies.allow_uri_forwarding = false;
  session_policies.maximum_session_duration = base::Hours(20);
  session_policies.curtain_required = false;
#endif
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
  session_policies.host_username_match_required = true;
#endif
  return session_policies;
}

const base::DictValue& GetFullSessionPolicyDict() {
  static const base::NoDestructor<base::DictValue> dict(
      base::DictValue()
          .Set(policy::key::kRemoteAccessHostClipboardSizeBytes, 1024)
          .Set(policy::key::kRemoteAccessHostFirewallTraversal, true)
          .Set(policy::key::kRemoteAccessHostAllowRelayedConnection, false)
          .Set(policy::key::kRemoteAccessHostUdpPortRange, "123-456")
#if !BUILDFLAG(IS_CHROMEOS)
          .Set(policy::key::kRemoteAccessHostAllowFileTransfer, true)
          .Set(policy::key::kRemoteAccessHostAllowUrlForwarding, false)
          .Set(policy::key::kRemoteAccessHostMaximumSessionDurationMinutes,
               1200)
          .Set(policy::key::kRemoteAccessHostRequireCurtain, false)
#endif
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
          .Set(policy::key::kRemoteAccessHostMatchUsername, true)
#endif
  );
  return *dict;
}

#if !BUILDFLAG(IS_CHROMEOS)
base::DictValue GetPolicyDictWithMaxDurationMins(int mins) {
  return GetFullSessionPolicyDict().Clone().Set(
      policy::key::kRemoteAccessHostMaximumSessionDurationMinutes, mins);
}
#endif

base::DictValue GetPolicyDictWithClipboardSize(int clipboard_size) {
  return GetFullSessionPolicyDict().Clone().Set(
      policy::key::kRemoteAccessHostClipboardSizeBytes, clipboard_size);
}

}  // namespace

TEST(SessionPoliciesFromDict, EmptyDict_CreatesEmptyPolicies) {
  std::optional<SessionPolicies> policies =
      SessionPoliciesFromDict(base::DictValue());
  ASSERT_TRUE(policies.has_value());
  EXPECT_EQ(*policies, SessionPolicies());
}

TEST(SessionPoliciesFromDict, FullDict_CreatesFullPolicies) {
  std::optional<SessionPolicies> policies =
      SessionPoliciesFromDict(GetFullSessionPolicyDict());
  ASSERT_TRUE(policies.has_value());
  EXPECT_EQ(*policies, GetFullSessionPolicies());
}

TEST(SessionPoliciesFromDict, FullDict_ExpectNoValueForAllowRemoteInput) {
  // `SessionPolicies.allow_remote_input` is not set from `PolicyWatcher` so we
  // expect the value to be empty.
  std::optional<SessionPolicies> policies =
      SessionPoliciesFromDict(GetFullSessionPolicyDict());
  ASSERT_TRUE(policies.has_value());
  EXPECT_FALSE(policies->allow_remote_input.has_value());
}

TEST(SessionPoliciesFromDict, PartialDict_CreatesPartialPolicies) {
  base::DictValue policy_dict = GetFullSessionPolicyDict().Clone();
  policy_dict.Remove(policy::key::kRemoteAccessHostClipboardSizeBytes);
  policy_dict.Remove(policy::key::kRemoteAccessHostUdpPortRange);

  std::optional<SessionPolicies> policies =
      SessionPoliciesFromDict(policy_dict);
  ASSERT_TRUE(policies.has_value());

  SessionPolicies expected_policies = GetFullSessionPolicies();
  expected_policies.clipboard_size_bytes.reset();
  expected_policies.host_udp_port_range = PortRange();
  EXPECT_EQ(*policies, expected_policies);
}

TEST(SessionPoliciesFromDict,
     FirewallTraversalDisabled_DisablesStunAndRelayedConnections) {
  base::DictValue policy_dict =
      GetFullSessionPolicyDict()
          .Clone()
          .Set(policy::key::kRemoteAccessHostFirewallTraversal, false)
          .Set(policy::key::kRemoteAccessHostAllowRelayedConnection, true);

  std::optional<SessionPolicies> policies =
      SessionPoliciesFromDict(policy_dict);
  ASSERT_TRUE(policies.has_value());

  SessionPolicies expected_policies = GetFullSessionPolicies();
  expected_policies.allow_stun_connections = false;
  expected_policies.allow_relayed_connections = false;
  EXPECT_EQ(*policies, expected_policies);
}

#if !BUILDFLAG(IS_CHROMEOS)
TEST(SessionPoliciesFromDict, NonPositiveMaxSessionDuration_FieldIsNullopt) {
  SessionPolicies expected_policies = GetFullSessionPolicies();
  expected_policies.maximum_session_duration.reset();
  std::optional<SessionPolicies> policies_negative =
      SessionPoliciesFromDict(GetPolicyDictWithMaxDurationMins(-1));
  ASSERT_TRUE(policies_negative.has_value());
  EXPECT_EQ(*policies_negative, expected_policies);

  std::optional<SessionPolicies> policies_zero =
      SessionPoliciesFromDict(GetPolicyDictWithMaxDurationMins(0));
  ASSERT_TRUE(policies_zero.has_value());
  EXPECT_EQ(*policies_zero, expected_policies);
}

TEST(SessionPoliciesFromDict, PositiveMaxSessionDuration_FieldIsPopulated) {
  SessionPolicies expected_policies = GetFullSessionPolicies();
  expected_policies.maximum_session_duration = base::Minutes(10);
  std::optional<SessionPolicies> policies =
      SessionPoliciesFromDict(GetPolicyDictWithMaxDurationMins(10));
  ASSERT_TRUE(policies.has_value());
  EXPECT_EQ(*policies, expected_policies);
}
#endif

TEST(SessionPoliciesFromDict, InvalidHostUdpPortRange_ReturnsNullopt) {
  base::DictValue policy_dict = GetFullSessionPolicyDict().Clone().Set(
      policy::key::kRemoteAccessHostUdpPortRange, "456-123");
  EXPECT_FALSE(SessionPoliciesFromDict(policy_dict).has_value());
}

TEST(SessionPoliciesFromDict, NegativeClipboardSize_FieldIsNullopt) {
  SessionPolicies expected_policies = GetFullSessionPolicies();
  expected_policies.clipboard_size_bytes.reset();
  std::optional<SessionPolicies> policies =
      SessionPoliciesFromDict(GetPolicyDictWithClipboardSize(-1));
  ASSERT_TRUE(policies.has_value());
  EXPECT_EQ(*policies, expected_policies);
}

TEST(SessionPoliciesFromDict, ZeroClipboardSize_FieldIsZero) {
  SessionPolicies expected_policies = GetFullSessionPolicies();
  expected_policies.clipboard_size_bytes = 0;
  std::optional<SessionPolicies> policies =
      SessionPoliciesFromDict(GetPolicyDictWithClipboardSize(0));
  ASSERT_TRUE(policies.has_value());
  EXPECT_EQ(*policies, expected_policies);
}

}  // namespace remoting
