// Copyright 2013 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 "components/policy/core/common/configuration_policy_provider_test.h"

#include <utility>

#include "base/bind.h"
#include "base/callback.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "components/policy/core/common/configuration_policy_provider.h"
#include "components/policy/core/common/external_data_fetcher.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_bundle.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_migrator.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_types.h"
#include "testing/gmock/include/gmock/gmock.h"

using ::testing::Mock;
using ::testing::_;

namespace policy {

const char kTestChromeSchema[] =
    "{"
    "  \"type\": \"object\","
    "  \"properties\": {"
    "    \"StringPolicy\": { \"type\": \"string\" },"
    "    \"BooleanPolicy\": { \"type\": \"boolean\" },"
    "    \"IntegerPolicy\": { \"type\": \"integer\" },"
    "    \"StringListPolicy\": {"
    "      \"type\": \"array\","
    "      \"items\": { \"type\": \"string\" }"
    "    },"
    "    \"DictionaryPolicy\": {"
    "      \"type\": \"object\","
    "      \"properties\": {"
    "        \"bool\": { \"type\": \"boolean\" },"
    "        \"double\": { \"type\": \"number\" },"
    "        \"int\": { \"type\": \"integer\" },"
    "        \"string\": { \"type\": \"string\" },"
    "        \"array\": {"
    "          \"type\": \"array\","
    "          \"items\": { \"type\": \"string\" }"
    "        },"
    "        \"dictionary\": {"
    "          \"type\": \"object\","
    "          \"properties\": {"
    "            \"sub\": { \"type\": \"string\" },"
    "            \"sublist\": {"
    "              \"type\": \"array\","
    "              \"items\": {"
    "                \"type\": \"object\","
    "                \"properties\": {"
    "                  \"aaa\": { \"type\": \"integer\" },"
    "                  \"bbb\": { \"type\": \"integer\" },"
    "                  \"ccc\": { \"type\": \"string\" },"
    "                  \"ddd\": { \"type\": \"string\" }"
    "                }"
    "              }"
    "            }"
    "          }"
    "        },"
    "        \"list\": {"
    "          \"type\": \"array\","
    "          \"items\": {"
    "            \"type\": \"object\","
    "            \"properties\": {"
    "              \"subdictindex\": { \"type\": \"integer\" },"
    "              \"subdict\": {"
    "                \"type\": \"object\","
    "                \"properties\": {"
    "                  \"bool\": { \"type\": \"boolean\" },"
    "                  \"double\": { \"type\": \"number\" },"
    "                  \"int\": { \"type\": \"integer\" },"
    "                  \"string\": { \"type\": \"string\" }"
    "                }"
    "              }"
    "            }"
    "          }"
    "        },"
    "        \"dict\": {"
    "          \"type\": \"object\","
    "          \"properties\": {"
    "            \"bool\": { \"type\": \"boolean\" },"
    "            \"double\": { \"type\": \"number\" },"
    "            \"int\": { \"type\": \"integer\" },"
    "            \"string\": { \"type\": \"string\" },"
    "            \"list\": {"
    "              \"type\": \"array\","
    "              \"items\": {"
    "                \"type\": \"object\","
    "                \"properties\": {"
    "                  \"subdictindex\": { \"type\": \"integer\" },"
    "                  \"subdict\": {"
    "                    \"type\": \"object\","
    "                    \"properties\": {"
    "                      \"bool\": { \"type\": \"boolean\" },"
    "                      \"double\": { \"type\": \"number\" },"
    "                      \"int\": { \"type\": \"integer\" },"
    "                      \"string\": { \"type\": \"string\" }"
    "                    }"
    "                  }"
    "                }"
    "              }"
    "            }"
    "          }"
    "        }"
    "      }"
    "    }"
    "  }"
    "}";

namespace test_keys {

const char kKeyString[] = "StringPolicy";
const char kKeyBoolean[] = "BooleanPolicy";
const char kKeyInteger[] = "IntegerPolicy";
const char kKeyStringList[] = "StringListPolicy";
const char kKeyDictionary[] = "DictionaryPolicy";

}  // namespace test_keys

PolicyTestBase::PolicyTestBase() {}

PolicyTestBase::~PolicyTestBase() {}

void PolicyTestBase::SetUp() {
  const PolicyNamespace ns(POLICY_DOMAIN_CHROME, "");
  ASSERT_TRUE(RegisterSchema(ns, kTestChromeSchema));
}

void PolicyTestBase::TearDown() {
  task_environment_.RunUntilIdle();
}

bool PolicyTestBase::RegisterSchema(const PolicyNamespace& ns,
                                    const std::string& schema_string) {
  std::string error;
  Schema schema = Schema::Parse(schema_string, &error);
  if (schema.valid()) {
    schema_registry_.RegisterComponent(ns, schema);
    return true;
  }
  ADD_FAILURE() << error;
  return false;
}

PolicyProviderTestHarness::PolicyProviderTestHarness(PolicyLevel level,
                                                     PolicyScope scope,
                                                     PolicySource source)
    : level_(level), scope_(scope), source_(source) {}

PolicyProviderTestHarness::~PolicyProviderTestHarness() {}

PolicyLevel PolicyProviderTestHarness::policy_level() const {
  return level_;
}

PolicyScope PolicyProviderTestHarness::policy_scope() const {
  return scope_;
}

PolicySource PolicyProviderTestHarness::policy_source() const {
  return source_;
}

void PolicyProviderTestHarness::Install3rdPartyPolicy(
    const base::DictionaryValue* policies) {
  FAIL();
}

ConfigurationPolicyProviderTest::ConfigurationPolicyProviderTest() {}

ConfigurationPolicyProviderTest::~ConfigurationPolicyProviderTest() {}

void ConfigurationPolicyProviderTest::SetUp() {
  PolicyTestBase::SetUp();

  test_harness_.reset((*GetParam())());
  ASSERT_NO_FATAL_FAILURE(test_harness_->SetUp());

  const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, "");
  Schema chrome_schema = *schema_registry_.schema_map()->GetSchema(chrome_ns);
  Schema extension_schema =
      chrome_schema.GetKnownProperty(test_keys::kKeyDictionary);
  ASSERT_TRUE(extension_schema.valid());
  schema_registry_.RegisterComponent(
      PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
                      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
      extension_schema);
  schema_registry_.RegisterComponent(
      PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
                      "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
      extension_schema);
  schema_registry_.RegisterComponent(
      PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
                      "cccccccccccccccccccccccccccccccc"),
      extension_schema);

  provider_.reset(test_harness_->CreateProvider(
      &schema_registry_,
      base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})));
  provider_->Init(&schema_registry_);
  // Some providers do a reload on init. Make sure any notifications generated
  // are fired now.
  task_environment_.RunUntilIdle();

  const PolicyBundle kEmptyBundle;
  EXPECT_TRUE(provider_->policies().Equals(kEmptyBundle));
}

void ConfigurationPolicyProviderTest::TearDown() {
  // Give providers the chance to clean up after themselves on the file thread.
  provider_->Shutdown();
  provider_.reset();

  PolicyTestBase::TearDown();
}

void ConfigurationPolicyProviderTest::CheckValue(
    const char* policy_name,
    const base::Value& expected_value,
    base::OnceClosure install_value) {
  // Install the value, reload policy and check the provider for the value.
  std::move(install_value).Run();
  provider_->RefreshPolicies();
  task_environment_.RunUntilIdle();
  PolicyBundle expected_bundle;
  expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
      .Set(policy_name, test_harness_->policy_level(),
           test_harness_->policy_scope(), test_harness_->policy_source(),
           expected_value.Clone(), nullptr);
  EXPECT_TRUE(provider_->policies().Equals(expected_bundle));
}

TEST_P(ConfigurationPolicyProviderTest, Empty) {
  provider_->RefreshPolicies();
  task_environment_.RunUntilIdle();
  const PolicyBundle kEmptyBundle;
  EXPECT_TRUE(provider_->policies().Equals(kEmptyBundle));
}

TEST_P(ConfigurationPolicyProviderTest, StringValue) {
  const char kTestString[] = "string_value";
  base::Value expected_value(kTestString);
  CheckValue(test_keys::kKeyString, expected_value,
             base::BindOnce(&PolicyProviderTestHarness::InstallStringPolicy,
                            base::Unretained(test_harness_.get()),
                            test_keys::kKeyString, kTestString));
}

TEST_P(ConfigurationPolicyProviderTest, BooleanValue) {
  base::Value expected_value(true);
  CheckValue(test_keys::kKeyBoolean, expected_value,
             base::BindOnce(&PolicyProviderTestHarness::InstallBooleanPolicy,
                            base::Unretained(test_harness_.get()),
                            test_keys::kKeyBoolean, true));
}

TEST_P(ConfigurationPolicyProviderTest, IntegerValue) {
  base::Value expected_value(42);
  CheckValue(test_keys::kKeyInteger, expected_value,
             base::BindOnce(&PolicyProviderTestHarness::InstallIntegerPolicy,
                            base::Unretained(test_harness_.get()),
                            test_keys::kKeyInteger, 42));
}

TEST_P(ConfigurationPolicyProviderTest, StringListValue) {
  base::ListValue expected_value;
  expected_value.Append("first");
  expected_value.Append("second");
  CheckValue(test_keys::kKeyStringList, expected_value,
             base::BindOnce(&PolicyProviderTestHarness::InstallStringListPolicy,
                            base::Unretained(test_harness_.get()),
                            test_keys::kKeyStringList, &expected_value));
}

TEST_P(ConfigurationPolicyProviderTest, DictionaryValue) {
  base::Value expected_value(base::Value::Type::DICTIONARY);
  expected_value.SetBoolKey("bool", true);
  expected_value.SetDoubleKey("double", 123.456);
  expected_value.SetIntKey("int", 123);
  expected_value.SetStringKey("string", "omg");

  {
    base::Value list(base::Value::Type::LIST);
    list.Append("first");
    list.Append("second");
    expected_value.SetKey("array", std::move(list));
  }

  base::Value sublist(base::Value::Type::LIST);
  {
    base::Value sub(base::Value::Type::DICTIONARY);
    sub.SetIntKey("aaa", 111);
    sub.SetIntKey("bbb", 222);
    sublist.Append(std::move(sub));
  }

  {
    base::Value sub(base::Value::Type::DICTIONARY);
    sub.SetStringKey("ccc", "333");
    sub.SetStringKey("ddd", "444");
    sublist.Append(std::move(sub));
  }

  base::Value dict(base::Value::Type::DICTIONARY);
  dict.SetStringKey("sub", "value");
  dict.SetKey("sublist", std::move(sublist));
  expected_value.SetKey("dictionary", std::move(dict));

  CheckValue(test_keys::kKeyDictionary, expected_value,
             base::BindOnce(&PolicyProviderTestHarness::InstallDictionaryPolicy,
                            base::Unretained(test_harness_.get()),
                            test_keys::kKeyDictionary, &expected_value));
}

TEST_P(ConfigurationPolicyProviderTest, RefreshPolicies) {
  PolicyBundle bundle;
  EXPECT_TRUE(provider_->policies().Equals(bundle));

  // OnUpdatePolicy is called even when there are no changes.
  MockConfigurationPolicyObserver observer;
  provider_->AddObserver(&observer);
  EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
  provider_->RefreshPolicies();
  task_environment_.RunUntilIdle();
  Mock::VerifyAndClearExpectations(&observer);

  EXPECT_TRUE(provider_->policies().Equals(bundle));

  // OnUpdatePolicy is called when there are changes.
  test_harness_->InstallStringPolicy(test_keys::kKeyString, "value");
  EXPECT_CALL(observer, OnUpdatePolicy(provider_.get())).Times(1);
  provider_->RefreshPolicies();
  task_environment_.RunUntilIdle();
  Mock::VerifyAndClearExpectations(&observer);

  bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
      .Set(test_keys::kKeyString, test_harness_->policy_level(),
           test_harness_->policy_scope(), test_harness_->policy_source(),
           base::Value("value"), nullptr);
  EXPECT_TRUE(provider_->policies().Equals(bundle));
  provider_->RemoveObserver(&observer);
}

Configuration3rdPartyPolicyProviderTest::
    Configuration3rdPartyPolicyProviderTest() {}

Configuration3rdPartyPolicyProviderTest::
    ~Configuration3rdPartyPolicyProviderTest() {}

TEST_P(Configuration3rdPartyPolicyProviderTest, Load3rdParty) {
  base::DictionaryValue policy_dict;
  policy_dict.SetBoolean("bool", true);
  policy_dict.SetDouble("double", 123.456);
  policy_dict.SetInteger("int", 789);
  policy_dict.SetString("string", "string value");

  base::ListValue list;
  for (int i = 0; i < 2; ++i) {
    auto dict = std::make_unique<base::DictionaryValue>();
    dict->SetInteger("subdictindex", i);
    dict->SetKey("subdict", policy_dict.Clone());
    list.Append(std::move(dict));
  }
  policy_dict.SetKey("list", std::move(list));
  policy_dict.SetKey("dict", policy_dict.Clone());

  // Install these policies as a Chrome policy.
  test_harness_->InstallDictionaryPolicy(test_keys::kKeyDictionary,
                                         &policy_dict);
  // Install them as 3rd party policies too.
  base::DictionaryValue policy_3rdparty;
  policy_3rdparty.SetPath({"extensions", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
                          policy_dict.Clone());
  policy_3rdparty.SetPath({"extensions", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},
                          policy_dict.Clone());
  // Install invalid 3rd party policies that shouldn't be loaded. These also
  // help detecting memory leaks in the code paths that detect invalid input.
  policy_3rdparty.SetPath({"invalid-domain", "component"}, policy_dict.Clone());
  policy_3rdparty.SetString("extensions.cccccccccccccccccccccccccccccccc",
                            "invalid-value");
  test_harness_->Install3rdPartyPolicy(&policy_3rdparty);

  provider_->RefreshPolicies();
  task_environment_.RunUntilIdle();

  PolicyMap expected_policy;
  expected_policy.Set(test_keys::kKeyDictionary, test_harness_->policy_level(),
                      test_harness_->policy_scope(),
                      test_harness_->policy_source(), policy_dict.Clone(),
                      nullptr);
  PolicyBundle expected_bundle;
  expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())) =
      expected_policy.Clone();
  expected_policy.Clear();
  expected_policy.LoadFrom(&policy_dict,
                           test_harness_->policy_level(),
                           test_harness_->policy_scope(),
                           test_harness_->policy_source());
  expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
                                      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) =
      expected_policy.Clone();
  expected_bundle.Get(PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
                                      "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")) =
      expected_policy.Clone();
  EXPECT_TRUE(provider_->policies().Equals(expected_bundle));
}

// These are abstract policy provider tests, which are instantiated for each
// policy provider implementation.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ConfigurationPolicyProviderTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
    Configuration3rdPartyPolicyProviderTest);

}  // namespace policy
