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

#include "components/search_engines/enterprise/default_search_policy_handler.h"

#include <memory>

#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "components/policy/core/browser/configuration_policy_pref_store.h"
#include "components/policy/core/browser/configuration_policy_pref_store_test.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/search_engines/default_search_manager.h"
#include "components/search_engines/search_engines_pref_names.h"
#include "components/search_engines/template_url_data.h"

namespace policy {

class DefaultSearchPolicyHandlerTest
    : public ConfigurationPolicyPrefStoreTest {
 public:
  DefaultSearchPolicyHandlerTest() {
    default_alternate_urls_.Append("http://www.google.com/#q={searchTerms}");
    default_alternate_urls_.Append(
        "http://www.google.com/search#q={searchTerms}");
  }

  void SetUp() override {
    handler_list_.AddHandler(base::WrapUnique<ConfigurationPolicyHandler>(
        new DefaultSearchPolicyHandler));
  }

 protected:
  static const char kSearchURL[];
  static const char kSuggestURL[];
  static const char kName[];
  static const char kReplacementKey[];
  static const char kImageURL[];
  static const char kImageParams[];
  static const char kFileSearchURL[];
  static const char kHostName[];
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
  static const char kKeyword[];
  static const char kNewTabURL[];
#endif

  // Build a default search policy by setting search-related keys in |policy| to
  // reasonable values. You can update any of the keys after calling this
  // method.
  void BuildDefaultSearchPolicy(PolicyMap* policy);

  base::ListValue default_alternate_urls_;
};

const char DefaultSearchPolicyHandlerTest::kSearchURL[] =
    "http://test.com/search?t={searchTerms}";
const char DefaultSearchPolicyHandlerTest::kSuggestURL[] =
    "http://test.com/sugg?={searchTerms}";
const char DefaultSearchPolicyHandlerTest::kName[] =
    "MyName";
const char DefaultSearchPolicyHandlerTest::kImageURL[] =
    "http://test.com/searchbyimage/upload";
const char DefaultSearchPolicyHandlerTest::kImageParams[] =
    "image_content=content,image_url=http://test.com/test.png";
const char DefaultSearchPolicyHandlerTest::kFileSearchURL[] =
    "file:///c:/path/to/search?t={searchTerms}";
const char DefaultSearchPolicyHandlerTest::kHostName[] = "test.com";
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
const char DefaultSearchPolicyHandlerTest::kKeyword[] = "MyKeyword";
const char DefaultSearchPolicyHandlerTest::kNewTabURL[] =
    "http://test.com/newtab";
#endif

void DefaultSearchPolicyHandlerTest::
    BuildDefaultSearchPolicy(PolicyMap* policy) {
  base::ListValue encodings;
  encodings.Append("UTF-16");
  encodings.Append("UTF-8");
  policy->Set(key::kDefaultSearchProviderEnabled, POLICY_LEVEL_MANDATORY,
              POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
              nullptr);
  policy->Set(key::kDefaultSearchProviderSearchURL, POLICY_LEVEL_MANDATORY,
              POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(kSearchURL),
              nullptr);
  policy->Set(key::kDefaultSearchProviderName, POLICY_LEVEL_MANDATORY,
              POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(kName),
              nullptr);
  policy->Set(key::kDefaultSearchProviderSuggestURL, POLICY_LEVEL_MANDATORY,
              POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(kSuggestURL),
              nullptr);
  policy->Set(key::kDefaultSearchProviderEncodings, POLICY_LEVEL_MANDATORY,
              POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
              base::Value(std::move(encodings)), nullptr);
  policy->Set(key::kDefaultSearchProviderAlternateURLs, POLICY_LEVEL_MANDATORY,
              POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
              base::Value(default_alternate_urls_.Clone()), nullptr);
  policy->Set(key::kDefaultSearchProviderImageURL, POLICY_LEVEL_MANDATORY,
              POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(kImageURL),
              nullptr);
  policy->Set(key::kDefaultSearchProviderImageURLPostParams,
              POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
              base::Value(kImageParams), nullptr);
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
  policy->Set(key::kDefaultSearchProviderKeyword, POLICY_LEVEL_MANDATORY,
              POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(kKeyword),
              nullptr);
  policy->Set(key::kDefaultSearchProviderNewTabURL, POLICY_LEVEL_MANDATORY,
              POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(kNewTabURL),
              nullptr);
#endif
}

// Checks that if the default search policy is missing, that no elements of the
// default search policy will be present.
TEST_F(DefaultSearchPolicyHandlerTest, MissingUrl) {
  PolicyMap policy;
  BuildDefaultSearchPolicy(&policy);
  policy.Erase(key::kDefaultSearchProviderSearchURL);
  UpdateProviderPolicy(policy);

  const base::Value* temp = nullptr;
  EXPECT_FALSE(store_->GetValue(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName, &temp));
}

// Checks that if the default search policy is invalid, that no elements of the
// default search policy will be present.
TEST_F(DefaultSearchPolicyHandlerTest, Invalid) {
  PolicyMap policy;
  BuildDefaultSearchPolicy(&policy);
  const char bad_search_url[] = "http://test.com/noSearchTerms";
  policy.Set(key::kDefaultSearchProviderSearchURL, POLICY_LEVEL_MANDATORY,
             POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
             base::Value(bad_search_url), nullptr);
  UpdateProviderPolicy(policy);

  const base::Value* temp = nullptr;
  EXPECT_FALSE(store_->GetValue(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName, &temp));
}

// Checks that if the default search policy has invalid type for elements,
// that no elements of the default search policy will be present in prefs.
TEST_F(DefaultSearchPolicyHandlerTest, InvalidType) {
  // List of policies defined in test policy.
  const char* kPolicyNamesToCheck[] = {
      key::kDefaultSearchProviderEnabled,
      key::kDefaultSearchProviderName,
      key::kDefaultSearchProviderSearchURL,
      key::kDefaultSearchProviderSuggestURL,
      key::kDefaultSearchProviderEncodings,
      key::kDefaultSearchProviderAlternateURLs,
      key::kDefaultSearchProviderImageURL,
      key::kDefaultSearchProviderImageURLPostParams,
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
      key::kDefaultSearchProviderKeyword,
      key::kDefaultSearchProviderNewTabURL,
#endif
  };

  PolicyMap policy;
  BuildDefaultSearchPolicy(&policy);

  for (auto* policy_name : kPolicyNamesToCheck) {
    // Check that policy can be successfully applied first.
    UpdateProviderPolicy(policy);
    const base::Value* temp = nullptr;
    EXPECT_TRUE(store_->GetValue(
        DefaultSearchManager::kDefaultSearchProviderDataPrefName, &temp));

    // It's safe to use `GetValueUnsafe()` as multiple policy types are handled.
    auto old_value = policy.GetValueUnsafe(policy_name)->Clone();
    // BinaryValue is not supported in any current default search policy params.
    // Try changing policy param to BinaryValue and check that policy becomes
    // invalid.
    policy.Set(policy_name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
               POLICY_SOURCE_CLOUD, base::Value(base::Value::Type::BINARY),
               nullptr);
    UpdateProviderPolicy(policy);

    EXPECT_FALSE(store_->GetValue(
        DefaultSearchManager::kDefaultSearchProviderDataPrefName, &temp))
        << "Policy type check failed " << policy_name;
    // Return old value to policy map.
    policy.Set(policy_name, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
               POLICY_SOURCE_CLOUD, std::move(old_value), nullptr);
  }
}

// Checks that for a fully defined search policy, all elements have been
// read properly into the dictionary pref.
TEST_F(DefaultSearchPolicyHandlerTest, FullyDefined) {
  PolicyMap policy;
  BuildDefaultSearchPolicy(&policy);
  UpdateProviderPolicy(policy);

  const base::Value* temp = nullptr;
  EXPECT_TRUE(store_->GetValue(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName, &temp));
  const base::DictValue* dictionary = temp->GetIfDict();
  ASSERT_TRUE(dictionary);

  ASSERT_EQ(
      dictionary->FindInt(DefaultSearchManager::kPolicyOrigin),
      static_cast<int>(TemplateURLData::PolicyOrigin::kDefaultSearchProvider));
  const std::string* value = nullptr;
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kURL));
  EXPECT_EQ(kSearchURL, *value);
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kShortName));
  EXPECT_EQ(kName, *value);
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kKeyword));
  EXPECT_EQ(kKeyword, *value);
#endif

  ASSERT_TRUE(
      value = dictionary->FindString(DefaultSearchManager::kSuggestionsURL));
  EXPECT_EQ(kSuggestURL, *value);

  base::ListValue encodings;
  encodings.Append("UTF-16");
  encodings.Append("UTF-8");
  const base::ListValue* list_value = nullptr;
  ASSERT_TRUE(list_value =
                  dictionary->FindList(DefaultSearchManager::kInputEncodings));
  EXPECT_EQ(encodings, *list_value);

  ASSERT_TRUE(list_value =
                  dictionary->FindList(DefaultSearchManager::kAlternateURLs));
  EXPECT_EQ(default_alternate_urls_, *list_value);

  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kImageURL));
  EXPECT_EQ(kImageURL, *value);

  ASSERT_TRUE(value = dictionary->FindString(
                  DefaultSearchManager::kImageURLPostParams));
  EXPECT_EQ(kImageParams, *value);

  ASSERT_TRUE(value = dictionary->FindString(
                  DefaultSearchManager::kSearchURLPostParams));
  EXPECT_EQ(std::string(), *value);

  EXPECT_TRUE(value = dictionary->FindString(
                  DefaultSearchManager::kSuggestionsURLPostParams));
  EXPECT_EQ(std::string(), *value);
}

// Checks that disabling default search is properly reflected the dictionary
// pref.
TEST_F(DefaultSearchPolicyHandlerTest, DisabledByPolicy) {
  PolicyMap policy;
  policy.Set(key::kDefaultSearchProviderEnabled, POLICY_LEVEL_MANDATORY,
             POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(false),
             nullptr);
  policy.Set(key::kDefaultSearchProviderSearchURL, POLICY_LEVEL_MANDATORY,
             POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
             base::Value("http://a/?{searchTerms}"), nullptr);
  UpdateProviderPolicy(policy);
  const base::Value* temp = nullptr;
  // Ignore any other search provider related policy in this case.
  EXPECT_FALSE(store_->GetValue(DefaultSearchManager::kURL, &temp));

  EXPECT_TRUE(store_->GetValue(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName, &temp));
  const base::DictValue* dictionary = temp->GetIfDict();
  ASSERT_TRUE(dictionary);
  std::optional<bool> disabled =
      dictionary->FindBool(DefaultSearchManager::kDisabledByPolicy);
  EXPECT_TRUE(disabled.has_value());
  EXPECT_TRUE(disabled.value());
}

// Check that when the default search enabled policy is not set, all other
// default search-related policies are ignored.
TEST_F(DefaultSearchPolicyHandlerTest, DisabledByPolicyNotSet) {
  PolicyMap policy;
  policy.Set(key::kDefaultSearchProviderSearchURL, POLICY_LEVEL_MANDATORY,
             POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
             base::Value("http://a/?{searchTerms}"), nullptr);
  UpdateProviderPolicy(policy);
  const base::Value* temp = nullptr;
  EXPECT_FALSE(store_->GetValue(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName, &temp));
  EXPECT_FALSE(store_->GetValue(DefaultSearchManager::kURL, &temp));
}

// Checks that if the policy for default search is valid, i.e. there's a
// search URL, that all the elements have been given proper defaults.
TEST_F(DefaultSearchPolicyHandlerTest, MinimallyDefined) {
  PolicyMap policy;
  policy.Set(key::kDefaultSearchProviderEnabled, POLICY_LEVEL_MANDATORY,
             POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
             nullptr);
  policy.Set(key::kDefaultSearchProviderSearchURL, POLICY_LEVEL_MANDATORY,
             POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(kSearchURL),
             nullptr);
  UpdateProviderPolicy(policy);

  const base::Value* temp = nullptr;
  EXPECT_TRUE(store_->GetValue(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName, &temp));
  const base::DictValue* dictionary = temp->GetIfDict();
  ASSERT_TRUE(dictionary);

  // Name and keyword should be derived from host.
  ASSERT_EQ(
      dictionary->FindInt(DefaultSearchManager::kPolicyOrigin),
      static_cast<int>(TemplateURLData::PolicyOrigin::kDefaultSearchProvider));
  const std::string* value = nullptr;
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kURL));
  EXPECT_EQ(kSearchURL, *value);
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kShortName));
  EXPECT_EQ(kHostName, *value);
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kKeyword));
  EXPECT_EQ(kHostName, *value);

  // Everything else should be set to the default value.
  ASSERT_TRUE(
      value = dictionary->FindString(DefaultSearchManager::kSuggestionsURL));
  EXPECT_EQ(std::string(), *value);
  const base::ListValue* list_value = nullptr;
  ASSERT_TRUE(list_value =
                  dictionary->FindList(DefaultSearchManager::kInputEncodings));
  EXPECT_EQ(base::ListValue(), *list_value);
  ASSERT_TRUE(list_value =
                  dictionary->FindList(DefaultSearchManager::kAlternateURLs));
  EXPECT_EQ(base::ListValue(), *list_value);
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kImageURL));
  EXPECT_EQ(std::string(), *value);
  ASSERT_TRUE(value = dictionary->FindString(
                  DefaultSearchManager::kImageURLPostParams));
  EXPECT_EQ(std::string(), *value);
  ASSERT_TRUE(value = dictionary->FindString(
                  DefaultSearchManager::kSearchURLPostParams));
  EXPECT_EQ(std::string(), *value);
  ASSERT_TRUE(value = dictionary->FindString(
                  DefaultSearchManager::kSuggestionsURLPostParams));
  EXPECT_EQ(std::string(), *value);
}

// Checks that setting a file URL as the default search is reflected properly in
// the dictionary pref.
TEST_F(DefaultSearchPolicyHandlerTest, FileURL) {
  PolicyMap policy;
  policy.Set(key::kDefaultSearchProviderEnabled, POLICY_LEVEL_MANDATORY,
             POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
             nullptr);
  policy.Set(key::kDefaultSearchProviderSearchURL, POLICY_LEVEL_MANDATORY,
             POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
             base::Value(kFileSearchURL), nullptr);
  UpdateProviderPolicy(policy);

  const base::Value* temp = nullptr;

  EXPECT_TRUE(store_->GetValue(
      DefaultSearchManager::kDefaultSearchProviderDataPrefName, &temp));
  const base::DictValue* dictionary = temp->GetIfDict();
  ASSERT_TRUE(dictionary);

  ASSERT_EQ(
      dictionary->FindInt(DefaultSearchManager::kPolicyOrigin),
      static_cast<int>(TemplateURLData::PolicyOrigin::kDefaultSearchProvider));
  const std::string* value = nullptr;
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kURL));
  EXPECT_EQ(kFileSearchURL, *value);
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kShortName));
  EXPECT_EQ("_", *value);
  ASSERT_TRUE(value = dictionary->FindString(DefaultSearchManager::kKeyword));
  EXPECT_EQ("_", *value);
}

}  // namespace policy
