blob: 72834b9df1d44b9d39dc04a8a3bfdf83db66c918 [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/policy/policy_loader_win.h"
#include <windows.h>
#include "base/json/json_writer.h"
#include "base/string16.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/win/registry.h"
#include "chrome/browser/policy/async_policy_provider.h"
#include "chrome/browser/policy/configuration_policy_provider_test.h"
#include "chrome/browser/policy/policy_bundle.h"
#include "chrome/browser/policy/policy_map.h"
#include "chrome/common/json_schema_constants.h"
#include "policy/policy_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace schema = json_schema_constants;
using base::win::RegKey;
using namespace policy::registry_constants;
namespace policy {
namespace {
const wchar_t kUnitTestRegistrySubKey[] = L"SOFTWARE\\Chromium Unit Tests";
const wchar_t kUnitTestMachineOverrideSubKey[] =
L"SOFTWARE\\Chromium Unit Tests\\HKLM Override";
const wchar_t kUnitTestUserOverrideSubKey[] =
L"SOFTWARE\\Chromium Unit Tests\\HKCU Override";
// Installs |value| in the given registry |path| and |hive|, under the key
// |name|. Returns false on errors.
// Some of the possible Value types are stored after a conversion (e.g. doubles
// are stored as strings), and can only be retrieved if a corresponding schema
// is written.
bool InstallValue(const base::Value& value,
HKEY hive,
const string16& path,
const string16& name) {
// KEY_ALL_ACCESS causes the ctor to create the key if it does not exist yet.
RegKey key(hive, path.c_str(), KEY_ALL_ACCESS);
switch (value.GetType()) {
case base::Value::TYPE_NULL:
return key.WriteValue(name.c_str(), L"") == ERROR_SUCCESS;
case base::Value::TYPE_BOOLEAN: {
bool bool_value;
if (!value.GetAsBoolean(&bool_value))
return false;
return key.WriteValue(name.c_str(), bool_value ? 1 : 0) == ERROR_SUCCESS;
}
case base::Value::TYPE_INTEGER: {
int int_value;
if (!value.GetAsInteger(&int_value))
return false;
return key.WriteValue(name.c_str(), int_value) == ERROR_SUCCESS;
}
case base::Value::TYPE_DOUBLE: {
double double_value;
if (!value.GetAsDouble(&double_value))
return false;
string16 str_value = UTF8ToUTF16(base::DoubleToString(double_value));
return key.WriteValue(name.c_str(), str_value.c_str()) == ERROR_SUCCESS;
}
case base::Value::TYPE_STRING: {
string16 str_value;
if (!value.GetAsString(&str_value))
return false;
return key.WriteValue(name.c_str(), str_value.c_str()) == ERROR_SUCCESS;
}
case base::Value::TYPE_DICTIONARY: {
const base::DictionaryValue* sub_dict = NULL;
if (!value.GetAsDictionary(&sub_dict))
return false;
for (base::DictionaryValue::Iterator it(*sub_dict);
it.HasNext(); it.Advance()) {
if (!InstallValue(it.value(), hive, path + kPathSep + name,
UTF8ToUTF16(it.key()))) {
return false;
}
}
return true;
}
case base::Value::TYPE_LIST: {
const base::ListValue* list = NULL;
if (!value.GetAsList(&list))
return false;
for (size_t i = 0; i < list->GetSize(); ++i) {
const base::Value* item;
if (!list->Get(i, &item))
return false;
if (!InstallValue(*item, hive, path + kPathSep + name,
base::UintToString16(i + 1))) {
return false;
}
}
return true;
}
case base::Value::TYPE_BINARY:
return false;
}
NOTREACHED();
return false;
}
// Builds a JSON schema that represents the types contained in |value|.
// Ownership is transferred to the caller.
base::DictionaryValue* BuildSchema(const base::Value& value) {
base::DictionaryValue* schema = new base::DictionaryValue();
switch (value.GetType()) {
case base::Value::TYPE_NULL:
schema->SetString(schema::kType, "null");
break;
case base::Value::TYPE_BOOLEAN:
schema->SetString(schema::kType, "boolean");
break;
case base::Value::TYPE_INTEGER:
schema->SetString(schema::kType, "integer");
break;
case base::Value::TYPE_DOUBLE:
schema->SetString(schema::kType, "number");
break;
case base::Value::TYPE_STRING:
schema->SetString(schema::kType, "string");
break;
case base::Value::TYPE_LIST: {
// Assumes every list element has the same type.
const base::ListValue* list = NULL;
if (value.GetAsList(&list) && !list->empty()) {
schema->SetString(schema::kType, "array");
schema->Set(schema::kItems, BuildSchema(**list->begin()));
}
break;
}
case base::Value::TYPE_DICTIONARY: {
const base::DictionaryValue* dict = NULL;
if (value.GetAsDictionary(&dict)) {
base::DictionaryValue* properties = new base::DictionaryValue();
for (base::DictionaryValue::Iterator it(*dict);
it.HasNext(); it.Advance()) {
properties->Set(it.key(), BuildSchema(it.value()));
}
schema->SetString(schema::kType, "object");
schema->Set(schema::kProperties, properties);
}
break;
}
case base::Value::TYPE_BINARY:
break;
}
return schema;
}
// Writes a JSON |schema| at the registry entry |name| at |path|
// in the given |hive|. Returns false on failure.
bool WriteSchema(const base::DictionaryValue& schema,
HKEY hive,
const string16& path,
const string16& name) {
std::string encoded;
base::JSONWriter::Write(&schema, &encoded);
if (encoded.empty())
return false;
string16 encoded16 = UTF8ToUTF16(encoded);
// KEY_ALL_ACCESS causes the ctor to create the key if it does not exist yet.
RegKey key(hive, path.c_str(), KEY_ALL_ACCESS);
return key.WriteValue(name.c_str(), encoded16.c_str()) == ERROR_SUCCESS;
}
// Builds a JSON schema for |value| and writes it at the registry entry |name|
// at |path| in the given |hive|. Returns false on failure.
bool InstallSchema(const base::Value& value,
HKEY hive,
const string16& path,
const string16& name) {
scoped_ptr<base::DictionaryValue> schema_dict(BuildSchema(value));
return WriteSchema(*schema_dict, hive, path, name);
}
// This class provides sandboxing and mocking for the parts of the Windows
// Registry implementing Group Policy. It prepares two temporary sandbox keys
// in |kUnitTestRegistrySubKey|, one for HKLM and one for HKCU. A test's calls
// to the registry are redirected by Windows to these sandboxes, allowing the
// tests to manipulate and access policy as if it were active, but without
// actually changing the parts of the Registry that are managed by Group
// Policy.
class ScopedGroupPolicyRegistrySandbox {
public:
ScopedGroupPolicyRegistrySandbox();
~ScopedGroupPolicyRegistrySandbox();
private:
void ActivateOverrides();
void RemoveOverrides();
// Deletes the sandbox keys.
void DeleteKeys();
// Keys are created for the lifetime of a test to contain
// the sandboxed HKCU and HKLM hives, respectively.
RegKey temp_hkcu_hive_key_;
RegKey temp_hklm_hive_key_;
DISALLOW_COPY_AND_ASSIGN(ScopedGroupPolicyRegistrySandbox);
};
class TestHarness : public PolicyProviderTestHarness {
public:
explicit TestHarness(HKEY hive, PolicyScope scope);
virtual ~TestHarness();
virtual void SetUp() OVERRIDE;
virtual ConfigurationPolicyProvider* CreateProvider(
const PolicyDefinitionList* policy_definition_list) OVERRIDE;
virtual void InstallEmptyPolicy() OVERRIDE;
virtual void InstallStringPolicy(const std::string& policy_name,
const std::string& policy_value) OVERRIDE;
virtual void InstallIntegerPolicy(const std::string& policy_name,
int policy_value) OVERRIDE;
virtual void InstallBooleanPolicy(const std::string& policy_name,
bool policy_value) OVERRIDE;
virtual void InstallStringListPolicy(
const std::string& policy_name,
const base::ListValue* policy_value) OVERRIDE;
virtual void InstallDictionaryPolicy(
const std::string& policy_name,
const base::DictionaryValue* policy_value) OVERRIDE;
virtual void Install3rdPartyPolicy(
const base::DictionaryValue* policies) OVERRIDE;
// Creates a harness instance that will install policy in HKCU or HKLM,
// respectively.
static PolicyProviderTestHarness* CreateHKCU();
static PolicyProviderTestHarness* CreateHKLM();
private:
HKEY hive_;
ScopedGroupPolicyRegistrySandbox registry_sandbox_;
DISALLOW_COPY_AND_ASSIGN(TestHarness);
};
ScopedGroupPolicyRegistrySandbox::ScopedGroupPolicyRegistrySandbox() {
// Cleanup any remnants of previous tests.
DeleteKeys();
// Create the subkeys to hold the overridden HKLM and HKCU
// policy settings.
temp_hklm_hive_key_.Create(HKEY_CURRENT_USER,
kUnitTestMachineOverrideSubKey,
KEY_ALL_ACCESS);
temp_hkcu_hive_key_.Create(HKEY_CURRENT_USER,
kUnitTestUserOverrideSubKey,
KEY_ALL_ACCESS);
ActivateOverrides();
}
ScopedGroupPolicyRegistrySandbox::~ScopedGroupPolicyRegistrySandbox() {
RemoveOverrides();
DeleteKeys();
}
void ScopedGroupPolicyRegistrySandbox::ActivateOverrides() {
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_LOCAL_MACHINE,
temp_hklm_hive_key_.Handle()));
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_CURRENT_USER,
temp_hkcu_hive_key_.Handle()));
}
void ScopedGroupPolicyRegistrySandbox::RemoveOverrides() {
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_LOCAL_MACHINE, 0));
ASSERT_HRESULT_SUCCEEDED(RegOverridePredefKey(HKEY_CURRENT_USER, 0));
}
void ScopedGroupPolicyRegistrySandbox::DeleteKeys() {
RegKey key(HKEY_CURRENT_USER, kUnitTestRegistrySubKey, KEY_ALL_ACCESS);
key.DeleteKey(L"");
}
TestHarness::TestHarness(HKEY hive, PolicyScope scope)
: PolicyProviderTestHarness(POLICY_LEVEL_MANDATORY, scope), hive_(hive) {}
TestHarness::~TestHarness() {}
void TestHarness::SetUp() {}
ConfigurationPolicyProvider* TestHarness::CreateProvider(
const PolicyDefinitionList* policy_list) {
scoped_ptr<AsyncPolicyLoader> loader(new PolicyLoaderWin(policy_list));
return new AsyncPolicyProvider(loader.Pass());
}
void TestHarness::InstallEmptyPolicy() {}
void TestHarness::InstallStringPolicy(const std::string& policy_name,
const std::string& policy_value) {
RegKey key(hive_, kRegistryMandatorySubKey, KEY_ALL_ACCESS);
key.WriteValue(UTF8ToUTF16(policy_name).c_str(),
UTF8ToUTF16(policy_value).c_str());
}
void TestHarness::InstallIntegerPolicy(const std::string& policy_name,
int policy_value) {
RegKey key(hive_, kRegistryMandatorySubKey, KEY_ALL_ACCESS);
key.WriteValue(UTF8ToUTF16(policy_name).c_str(),
static_cast<DWORD>(policy_value));
}
void TestHarness::InstallBooleanPolicy(const std::string& policy_name,
bool policy_value) {
RegKey key(hive_, kRegistryMandatorySubKey, KEY_ALL_ACCESS);
key.WriteValue(UTF8ToUTF16(policy_name).c_str(),
static_cast<DWORD>(policy_value));
}
void TestHarness::InstallStringListPolicy(const std::string& policy_name,
const base::ListValue* policy_value) {
RegKey key(hive_,
(string16(kRegistryMandatorySubKey) + ASCIIToUTF16("\\") +
UTF8ToUTF16(policy_name)).c_str(),
KEY_ALL_ACCESS);
int index = 1;
for (base::ListValue::const_iterator element(policy_value->begin());
element != policy_value->end();
++element) {
std::string element_value;
if (!(*element)->GetAsString(&element_value))
continue;
std::string name(base::IntToString(index++));
key.WriteValue(UTF8ToUTF16(name).c_str(),
UTF8ToUTF16(element_value).c_str());
}
}
void TestHarness::InstallDictionaryPolicy(
const std::string& policy_name,
const base::DictionaryValue* policy_value) {
std::string json;
base::JSONWriter::Write(policy_value, &json);
RegKey key(hive_, kRegistryMandatorySubKey, KEY_ALL_ACCESS);
key.WriteValue(UTF8ToUTF16(policy_name).c_str(),
UTF8ToUTF16(json).c_str());
}
void TestHarness::Install3rdPartyPolicy(const base::DictionaryValue* policies) {
// The first level entries are domains, and the second level entries map
// components to their policy.
const string16 kPathPrefix = string16(kRegistryMandatorySubKey) + kPathSep +
kThirdParty + kPathSep;
for (base::DictionaryValue::Iterator domain(*policies);
domain.HasNext(); domain.Advance()) {
const base::DictionaryValue* components = NULL;
if (!domain.value().GetAsDictionary(&components)) {
ADD_FAILURE();
continue;
}
for (base::DictionaryValue::Iterator component(*components);
component.HasNext(); component.Advance()) {
const string16 path = string16(kRegistryMandatorySubKey) + kPathSep +
kThirdParty + kPathSep +
UTF8ToUTF16(domain.key()) + kPathSep +
UTF8ToUTF16(component.key());
InstallValue(component.value(), hive_, path, kMandatory);
EXPECT_TRUE(InstallSchema(component.value(), hive_, path, kSchema));
}
}
}
// static
PolicyProviderTestHarness* TestHarness::CreateHKCU() {
return new TestHarness(HKEY_CURRENT_USER, POLICY_SCOPE_USER);
}
// static
PolicyProviderTestHarness* TestHarness::CreateHKLM() {
return new TestHarness(HKEY_LOCAL_MACHINE, POLICY_SCOPE_MACHINE);
}
} // namespace
// Instantiate abstract test case for basic policy reading tests.
INSTANTIATE_TEST_CASE_P(
PolicyProviderWinTest,
ConfigurationPolicyProviderTest,
testing::Values(TestHarness::CreateHKCU, TestHarness::CreateHKLM));
// Instantiate abstract test case for 3rd party policy reading tests.
INSTANTIATE_TEST_CASE_P(
ThirdPartyPolicyProviderWinTest,
Configuration3rdPartyPolicyProviderTest,
testing::Values(TestHarness::CreateHKCU, TestHarness::CreateHKLM));
// Test cases for windows policy provider specific functionality.
class PolicyLoaderWinTest : public PolicyTestBase {
protected:
PolicyLoaderWinTest() {}
virtual ~PolicyLoaderWinTest() {}
bool Matches(const PolicyBundle& expected) {
PolicyLoaderWin loader(&test_policy_definitions::kList);
scoped_ptr<PolicyBundle> loaded(loader.Load());
return loaded->Equals(expected);
}
ScopedGroupPolicyRegistrySandbox registry_sandbox_;
};
TEST_F(PolicyLoaderWinTest, HKLMOverHKCU) {
RegKey hklm_key(HKEY_LOCAL_MACHINE, kRegistryMandatorySubKey, KEY_ALL_ACCESS);
hklm_key.WriteValue(UTF8ToUTF16(test_policy_definitions::kKeyString).c_str(),
UTF8ToUTF16("hklm").c_str());
RegKey hkcu_key(HKEY_CURRENT_USER, kRegistryMandatorySubKey, KEY_ALL_ACCESS);
hkcu_key.WriteValue(UTF8ToUTF16(test_policy_definitions::kKeyString).c_str(),
UTF8ToUTF16("hkcu").c_str());
PolicyBundle expected;
expected.Get(POLICY_DOMAIN_CHROME, "")
.Set(test_policy_definitions::kKeyString,
POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_MACHINE,
base::Value::CreateStringValue("hklm"));
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, Load3rdPartyWithoutSchema) {
base::DictionaryValue dict;
dict.SetString("str", "string value");
dict.SetInteger("int", 123);
dict.Set("subdict", dict.DeepCopy());
dict.Set("subsubdict", dict.DeepCopy());
dict.Set("subsubsubdict", dict.DeepCopy());
base::DictionaryValue policy_dict;
policy_dict.Set("extensions.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.policy",
dict.DeepCopy());
policy_dict.Set("extensions.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.policy",
dict.DeepCopy());
EXPECT_TRUE(InstallValue(policy_dict, HKEY_LOCAL_MACHINE,
kRegistryMandatorySubKey, kThirdParty));
PolicyBundle expected;
expected.Get(POLICY_DOMAIN_EXTENSIONS, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
.LoadFrom(&dict, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
expected.Get(POLICY_DOMAIN_EXTENSIONS, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
.LoadFrom(&dict, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, Merge3rdPartyPolicies) {
// Policy for the same extension will be provided at the 4 level/scope
// combinations, to verify that they overlap as expected.
const string16 kPathSuffix =
kRegistryMandatorySubKey + ASCIIToUTF16("\\3rdparty\\extensions\\merge");
const char kUserMandatory[] = "user-mandatory";
const char kUserRecommended[] = "user-recommended";
const char kMachineMandatory[] = "machine-mandatory";
const char kMachineRecommended[] = "machine-recommended";
base::DictionaryValue policy;
policy.SetString("a", kMachineMandatory);
EXPECT_TRUE(InstallValue(policy, HKEY_LOCAL_MACHINE,
kPathSuffix, kMandatory));
policy.SetString("a", kUserMandatory);
policy.SetString("b", kUserMandatory);
EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER,
kPathSuffix, kMandatory));
policy.SetString("a", kMachineRecommended);
policy.SetString("b", kMachineRecommended);
policy.SetString("c", kMachineRecommended);
EXPECT_TRUE(InstallValue(policy, HKEY_LOCAL_MACHINE,
kPathSuffix, kRecommended));
policy.SetString("a", kUserRecommended);
policy.SetString("b", kUserRecommended);
policy.SetString("c", kUserRecommended);
policy.SetString("d", kUserRecommended);
EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER,
kPathSuffix, kRecommended));
PolicyBundle expected;
PolicyMap& expected_policy = expected.Get(POLICY_DOMAIN_EXTENSIONS, "merge");
expected_policy.Set("a", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
base::Value::CreateStringValue(kMachineMandatory));
expected_policy.Set("b", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
base::Value::CreateStringValue(kUserMandatory));
expected_policy.Set("c", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_MACHINE,
base::Value::CreateStringValue(kMachineRecommended));
expected_policy.Set("d", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
base::Value::CreateStringValue(kUserRecommended));
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, LoadStringEncodedValues) {
// Create a dictionary with all the types that can be stored encoded in a
// string, to pass to InstallSchema(). Also build an equivalent dictionary
// with the encoded values, to pass to InstallValue().
base::DictionaryValue policy;
policy.Set("null", base::Value::CreateNullValue());
policy.SetBoolean("bool", true);
policy.SetInteger("int", -123);
policy.SetDouble("double", 456.78e9);
base::ListValue list;
list.Append(policy.DeepCopy());
list.Append(policy.DeepCopy());
policy.Set("list", list.DeepCopy());
// Encode |policy| before adding the "dict" entry.
std::string encoded_dict;
base::JSONWriter::Write(&policy, &encoded_dict);
ASSERT_FALSE(encoded_dict.empty());
policy.Set("dict", policy.DeepCopy());
std::string encoded_list;
base::JSONWriter::Write(&list, &encoded_list);
ASSERT_FALSE(encoded_list.empty());
base::DictionaryValue encoded_policy;
encoded_policy.SetString("null", "");
encoded_policy.SetString("bool", "1");
encoded_policy.SetString("int", "-123");
encoded_policy.SetString("double", "456.78e9");
encoded_policy.SetString("list", encoded_list);
encoded_policy.SetString("dict", encoded_dict);
const string16 kPathSuffix =
kRegistryMandatorySubKey + ASCIIToUTF16("\\3rdparty\\extensions\\string");
EXPECT_TRUE(InstallSchema(policy, HKEY_CURRENT_USER, kPathSuffix, kSchema));
EXPECT_TRUE(
InstallValue(encoded_policy, HKEY_CURRENT_USER, kPathSuffix, kMandatory));
PolicyBundle expected;
expected.Get(POLICY_DOMAIN_EXTENSIONS, "string")
.LoadFrom(&policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, LoadIntegerEncodedValues) {
base::DictionaryValue policy;
policy.SetBoolean("bool", true);
policy.SetInteger("int", 123);
policy.SetDouble("double", 456.0);
base::DictionaryValue encoded_policy;
encoded_policy.SetInteger("bool", 1);
encoded_policy.SetInteger("int", 123);
encoded_policy.SetInteger("double", 456);
const string16 kPathSuffix =
kRegistryMandatorySubKey + ASCIIToUTF16("\\3rdparty\\extensions\\int");
EXPECT_TRUE(InstallSchema(policy, HKEY_CURRENT_USER, kPathSuffix, kSchema));
EXPECT_TRUE(
InstallValue(encoded_policy, HKEY_CURRENT_USER, kPathSuffix, kMandatory));
PolicyBundle expected;
expected.Get(POLICY_DOMAIN_EXTENSIONS, "int")
.LoadFrom(&policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
EXPECT_TRUE(Matches(expected));
}
TEST_F(PolicyLoaderWinTest, DefaultPropertySchemaType) {
// Build a schema for an "object" with a default schema for its properties.
base::DictionaryValue default_schema;
default_schema.SetString(schema::kType, "number");
base::DictionaryValue integer_schema;
integer_schema.SetString(schema::kType, "integer");
base::DictionaryValue properties;
properties.Set("special-int1", integer_schema.DeepCopy());
properties.Set("special-int2", integer_schema.DeepCopy());
base::DictionaryValue schema;
schema.SetString(schema::kType, "object");
schema.Set(schema::kProperties, properties.DeepCopy());
schema.Set(schema::kAdditionalProperties, default_schema.DeepCopy());
const string16 kPathSuffix =
kRegistryMandatorySubKey + ASCIIToUTF16("\\3rdparty\\extensions\\test");
EXPECT_TRUE(WriteSchema(schema, HKEY_CURRENT_USER, kPathSuffix, kSchema));
// Write some test values.
base::DictionaryValue policy;
// These special values have a specific schema for them.
policy.SetInteger("special-int1", 123);
policy.SetString("special-int2", "-456");
// Other values default to be loaded as doubles.
policy.SetInteger("double1", 789.0);
policy.SetString("double2", "123.456e7");
policy.SetString("invalid", "omg");
EXPECT_TRUE(InstallValue(policy, HKEY_CURRENT_USER, kPathSuffix, kMandatory));
base::DictionaryValue expected_policy;
expected_policy.SetInteger("special-int1", 123);
expected_policy.SetInteger("special-int2", -456);
expected_policy.SetDouble("double1", 789.0);
expected_policy.SetDouble("double2", 123.456e7);
PolicyBundle expected;
expected.Get(POLICY_DOMAIN_EXTENSIONS, "test")
.LoadFrom(&expected_policy, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER);
EXPECT_TRUE(Matches(expected));
}
} // namespace policy