blob: 308f5d0dc69b452ae7bd598f7cfb1a7a10f19800 [file] [log] [blame]
// Copyright 2013 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/policy/core/common/schema.h"
#include <stddef.h>
#include <array>
#include <memory>
#include <utility>
#include "base/strings/stringprintf.h"
#include "base/types/expected_macros.h"
#include "base/values.h"
#include "components/policy/core/common/schema.h"
#include "components/policy/core/common/schema_internal.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace policy {
namespace {
#define TestSchemaValidation(a, b, c, d) \
TestSchemaValidationHelper( \
base::StringPrintf("%s:%i", __FILE__, __LINE__), a, b, c, d)
const char kTestSchema[] = R"({
"type": "object",
"properties": {
"Boolean": { "type": "boolean" },
"Integer": { "type": "integer" },
"Number": { "type": "number" },
"String": { "type": "string" },
"Array": {
"type": "array",
"items": { "type": "string" }
},
"ArrayOfObjects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"one": { "type": "string" },
"two": { "type": "integer" }
}
}
},
"ArrayOfArray": {
"type": "array",
"items": {
"type": "array",
"items": { "type": "string" }
}
},
"Object": {
"type": "object",
"properties": {
"one": { "type": "boolean" },
"two": { "type": "integer" }
},
"additionalProperties": { "type": "string" }
},
"ObjectOfObject": {
"type": "object",
"properties": {
"Object": {
"type": "object",
"properties": {
"one": { "type": "string" },
"two": { "type": "integer" }
}
}
}
},
"IntegerWithEnums": {
"type": "integer",
"enum": [1, 2, 3]
},
"IntegerWithEnumsGaps": {
"type": "integer",
"enum": [10, 20, 30]
},
"StringWithEnums": {
"type": "string",
"enum": ["one", "two", "three"]
},
"IntegerWithRange": {
"type": "integer",
"minimum": 1,
"maximum": 3
},
"ObjectOfArray": {
"type": "object",
"properties": {
"List": {
"type": "array",
"items": { "type": "integer" }
}
}
},
"ArrayOfObjectOfArray": {
"type": "array",
"items": {
"type": "object",
"properties": {
"List": {
"type": "array",
"items": { "type": "string" }
}
}
}
},
"StringWithPattern": {
"type": "string",
"pattern": "^foo+$"
},
"ObjectWithPatternProperties": {
"type": "object",
"patternProperties": {
"^foo+$": { "type": "integer" },
"^bar+$": {
"type": "string",
"enum": ["one", "two"]
}
},
"properties": {
"bar": {
"type": "string",
"enum": ["one", "three"]
}
}
},
"ObjectWithRequiredProperties": {
"type": "object",
"properties": {
"Integer": {
"type": "integer",
"enum": [1, 2]
},
"String": { "type": "string" },
"Number": { "type": "number" }
},
"patternProperties": {
"^Integer": {
"type": "integer",
"enum": [1, 3]
}
},
"required": [ "Integer", "String" ]
}
}
})";
bool ParseFails(const std::string& content) {
ASSIGN_OR_RETURN(const auto schema, Schema::Parse(content),
[](const auto& e) { return true; });
return false;
}
void TestSchemaValidationHelper(const std::string& source,
const Schema& schema,
const base::Value& value,
SchemaOnErrorStrategy strategy,
bool expected_return_value) {
std::string error;
static const char kNoErrorReturned[] = "No error returned.";
// Test that Schema::Validate() works as expected.
error = kNoErrorReturned;
bool returned = schema.Validate(value, strategy, nullptr, &error);
ASSERT_EQ(expected_return_value, returned) << source << ": " << error;
// Test that Schema::Normalize() will return the same value as
// Schema::Validate().
error = kNoErrorReturned;
base::Value cloned_value(value.Clone());
bool touched = false;
returned =
schema.Normalize(&cloned_value, strategy, nullptr, &error, &touched);
EXPECT_EQ(expected_return_value, returned) << source << ": " << error;
if (strategy == SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING)
return;
bool strictly_valid = schema.Validate(value, SCHEMA_STRICT, nullptr, &error);
EXPECT_EQ(touched, !strictly_valid && returned) << source;
// Test that Schema::Normalize() have actually dropped invalid and unknown
// properties.
if (expected_return_value) {
EXPECT_TRUE(schema.Validate(cloned_value, SCHEMA_STRICT, nullptr, &error))
<< source;
EXPECT_TRUE(schema.Normalize(&cloned_value, SCHEMA_STRICT, nullptr, &error,
nullptr))
<< source;
}
}
void TestSchemaValidationWithPath(const Schema& schema,
const base::Value& value,
PolicyErrorPath expected_failure_path) {
PolicyErrorPath error_path;
std::string error;
bool returned = schema.Validate(value, SCHEMA_STRICT, &error_path, &error);
ASSERT_FALSE(returned) << error_path.size();
EXPECT_EQ(error_path, expected_failure_path);
}
std::string SchemaObjectWrapper(const std::string& subschema) {
return "{"
" \"type\": \"object\","
" \"properties\": {"
" \"SomePropertyName\":" + subschema +
" }"
"}";
}
} // namespace
TEST(SchemaTest, MinimalSchema) {
EXPECT_FALSE(ParseFails(R"({ "type": "object" })"));
}
TEST(SchemaTest, InvalidSchemas) {
EXPECT_TRUE(ParseFails(""));
EXPECT_TRUE(ParseFails("omg"));
EXPECT_TRUE(ParseFails("\"omg\""));
EXPECT_TRUE(ParseFails("123"));
EXPECT_TRUE(ParseFails("[]"));
EXPECT_TRUE(ParseFails("null"));
EXPECT_TRUE(ParseFails("{}"));
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"additionalProperties": { "type":"object" }
})"));
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"patternProperties": { "a+b*": { "type": "object" } }
})"));
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"properties": { "Policy": { "type": "bogus" } }
})"));
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"properties": { "Policy": { "type": ["string", "number"] } }
})"));
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"properties": { "Policy": { "type": "any" } }
})"));
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"properties": { "Policy": 123 }
})"));
EXPECT_FALSE(ParseFails(R"({
"type": "object",
"unknown attribute": "is ignored"
})"));
}
TEST(SchemaTest, Ownership) {
const auto schema = Schema::Parse(R"({
"type": "object",
"properties": {
"sub": {
"type": "object",
"properties": {
"subsub": { "type": "string" }
}
}
}
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
Schema sub = schema->GetKnownProperty("sub");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::DICT, sub.type());
{
Schema::Iterator it = sub.GetPropertiesIterator();
ASSERT_FALSE(it.IsAtEnd());
EXPECT_STREQ("subsub", it.key());
sub = it.schema();
it.Advance();
EXPECT_TRUE(it.IsAtEnd());
}
ASSERT_TRUE(sub.valid());
EXPECT_EQ(base::Value::Type::STRING, sub.type());
// This test shouldn't leak nor use invalid memory.
}
TEST(SchemaTest, ValidSchema) {
const auto schema = Schema::Parse(kTestSchema);
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
EXPECT_FALSE(schema->GetProperty("invalid").valid());
Schema sub = schema->GetProperty("Boolean");
ASSERT_TRUE(sub.valid());
EXPECT_EQ(base::Value::Type::BOOLEAN, sub.type());
sub = schema->GetProperty("Integer");
ASSERT_TRUE(sub.valid());
EXPECT_EQ(base::Value::Type::INTEGER, sub.type());
sub = schema->GetProperty("Number");
ASSERT_TRUE(sub.valid());
EXPECT_EQ(base::Value::Type::DOUBLE, sub.type());
sub = schema->GetProperty("String");
ASSERT_TRUE(sub.valid());
EXPECT_EQ(base::Value::Type::STRING, sub.type());
sub = schema->GetProperty("Array");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::LIST, sub.type());
sub = sub.GetItems();
ASSERT_TRUE(sub.valid());
EXPECT_EQ(base::Value::Type::STRING, sub.type());
sub = schema->GetProperty("ArrayOfObjects");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::LIST, sub.type());
sub = sub.GetItems();
ASSERT_TRUE(sub.valid());
EXPECT_EQ(base::Value::Type::DICT, sub.type());
Schema subsub = sub.GetProperty("one");
ASSERT_TRUE(subsub.valid());
EXPECT_EQ(base::Value::Type::STRING, subsub.type());
subsub = sub.GetProperty("two");
ASSERT_TRUE(subsub.valid());
EXPECT_EQ(base::Value::Type::INTEGER, subsub.type());
subsub = sub.GetProperty("invalid");
EXPECT_FALSE(subsub.valid());
sub = schema->GetProperty("ArrayOfArray");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::LIST, sub.type());
sub = sub.GetItems();
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::LIST, sub.type());
sub = sub.GetItems();
ASSERT_TRUE(sub.valid());
EXPECT_EQ(base::Value::Type::STRING, sub.type());
sub = schema->GetProperty("Object");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::DICT, sub.type());
subsub = sub.GetProperty("one");
ASSERT_TRUE(subsub.valid());
EXPECT_EQ(base::Value::Type::BOOLEAN, subsub.type());
subsub = sub.GetProperty("two");
ASSERT_TRUE(subsub.valid());
EXPECT_EQ(base::Value::Type::INTEGER, subsub.type());
subsub = sub.GetProperty("undeclared");
ASSERT_TRUE(subsub.valid());
EXPECT_EQ(base::Value::Type::STRING, subsub.type());
sub = schema->GetProperty("IntegerWithEnums");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::INTEGER, sub.type());
sub = schema->GetProperty("IntegerWithEnumsGaps");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::INTEGER, sub.type());
sub = schema->GetProperty("StringWithEnums");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::STRING, sub.type());
sub = schema->GetProperty("IntegerWithRange");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::INTEGER, sub.type());
sub = schema->GetProperty("StringWithPattern");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::STRING, sub.type());
sub = schema->GetProperty("ObjectWithPatternProperties");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::DICT, sub.type());
sub = schema->GetProperty("ObjectWithRequiredProperties");
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::DICT, sub.type());
struct {
const char* expected_key;
base::Value::Type expected_type;
} kExpectedProperties[] = {
{"Array", base::Value::Type::LIST},
{"ArrayOfArray", base::Value::Type::LIST},
{"ArrayOfObjectOfArray", base::Value::Type::LIST},
{"ArrayOfObjects", base::Value::Type::LIST},
{"Boolean", base::Value::Type::BOOLEAN},
{"Integer", base::Value::Type::INTEGER},
{"IntegerWithEnums", base::Value::Type::INTEGER},
{"IntegerWithEnumsGaps", base::Value::Type::INTEGER},
{"IntegerWithRange", base::Value::Type::INTEGER},
{"Number", base::Value::Type::DOUBLE},
{"Object", base::Value::Type::DICT},
{"ObjectOfArray", base::Value::Type::DICT},
{"ObjectOfObject", base::Value::Type::DICT},
{"ObjectWithPatternProperties", base::Value::Type::DICT},
{"ObjectWithRequiredProperties", base::Value::Type::DICT},
{"String", base::Value::Type::STRING},
{"StringWithEnums", base::Value::Type::STRING},
{"StringWithPattern", base::Value::Type::STRING},
};
Schema::Iterator it = schema->GetPropertiesIterator();
for (const auto& props : kExpectedProperties) {
ASSERT_FALSE(it.IsAtEnd());
EXPECT_STREQ(props.expected_key, it.key());
ASSERT_TRUE(it.schema().valid());
EXPECT_EQ(props.expected_type, it.schema().type());
it.Advance();
}
EXPECT_TRUE(it.IsAtEnd());
}
TEST(SchemaTest, Lookups) {
{
const auto schema = Schema::Parse(R"({ "type": "object" })");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
// This empty schema should never find named properties.
EXPECT_FALSE(schema->GetKnownProperty("").valid());
EXPECT_FALSE(schema->GetKnownProperty("xyz").valid());
EXPECT_TRUE(schema->GetRequiredProperties().empty());
EXPECT_TRUE(schema->GetPatternProperties("").empty());
EXPECT_FALSE(schema->GetAdditionalProperties().valid());
EXPECT_TRUE(schema->GetPropertiesIterator().IsAtEnd());
}
{
const auto schema = Schema::Parse(R"({
"type": "object",
"properties": {
"Boolean": { "type": "boolean" }
}
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
EXPECT_FALSE(schema->GetKnownProperty("").valid());
EXPECT_FALSE(schema->GetKnownProperty("xyz").valid());
EXPECT_TRUE(schema->GetKnownProperty("Boolean").valid());
}
{
const auto schema = Schema::Parse(R"({
"type": "object",
"properties": {
"aa" : { "type": "boolean" },
"abab" : { "type": "string" },
"ab" : { "type": "number" },
"aba" : { "type": "integer" }
}
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
EXPECT_FALSE(schema->GetKnownProperty("").valid());
EXPECT_FALSE(schema->GetKnownProperty("xyz").valid());
struct {
const char* expected_key;
base::Value::Type expected_type;
} kExpectedKeys[] = {
{"aa", base::Value::Type::BOOLEAN},
{"ab", base::Value::Type::DOUBLE},
{"aba", base::Value::Type::INTEGER},
{"abab", base::Value::Type::STRING},
};
for (const auto& key : kExpectedKeys) {
Schema sub = schema->GetKnownProperty(key.expected_key);
ASSERT_TRUE(sub.valid());
EXPECT_EQ(key.expected_type, sub.type());
}
}
{
const auto schema = Schema::Parse(R"(
{
"type": "object",
"properties": {
"String": { "type": "string" },
"Object": {
"type": "object",
"properties": {"Integer": {"type": "integer"}},
"required": [ "Integer" ]
},
"Number": { "type": "number" }
},
"required": [ "String", "Object"]
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
EXPECT_EQ(std::vector<std::string>({"String", "Object"}),
schema->GetRequiredProperties());
Schema object = schema->GetKnownProperty("Object");
ASSERT_TRUE(object.valid());
ASSERT_EQ(base::Value::Type::DICT, object.type());
EXPECT_EQ(std::vector<std::string>({"Integer"}),
object.GetRequiredProperties());
}
}
TEST(SchemaTest, Wrap) {
const internal::SchemaNode kSchemas[] = {
{base::Value::Type::DICT, 0}, // 0: root node
{base::Value::Type::BOOLEAN, -1}, // 1
{base::Value::Type::INTEGER, -1}, // 2
{base::Value::Type::DOUBLE, -1}, // 3
{base::Value::Type::STRING, -1}, // 4
{base::Value::Type::LIST, 4}, // 5: list of strings.
{base::Value::Type::LIST, 5}, // 6: list of lists of strings.
{base::Value::Type::INTEGER, 0}, // 7: integer enumerations.
{base::Value::Type::INTEGER, 1}, // 8: ranged integers.
{base::Value::Type::STRING, 2}, // 9: string enumerations.
{base::Value::Type::STRING, 3}, // 10: string with pattern.
{base::Value::Type::DICT, 1}, // 11: dictionary with required
// properties
};
const internal::PropertyNode kPropertyNodes[] = {
{ "Boolean", 1}, // 0
{ "DictRequired", 11}, // 1
{ "Integer", 2}, // 2
{ "List", 5}, // 3
{ "Number", 3}, // 4
{ "String", 4}, // 5
{ "IntEnum", 7}, // 6
{ "RangedInt", 8}, // 7
{ "StrEnum", 9}, // 8
{ "StrPat", 10}, // 9
{ "bar+$", 4}, // 10
{ "String", 4}, // 11
{ "Number", 3}, // 12
};
const internal::PropertiesNode kProperties[] = {
// 0 to 10 (exclusive) are the known properties in kPropertyNodes, 9 is
// patternProperties and 6 is the additionalProperties node.
{ 0, 10, 11, 0, 0, 6 },
// 11 to 13 (exclusive) are the known properties in kPropertyNodes. 0 to
// 1 (exclusive) are the required properties in kRequired. -1 indicates
// no additionalProperties.
{ 11, 13, 13, 0, 1, -1 },
};
const internal::RestrictionNode kRestriction[] = {
{{0, 3}}, // 0: [1, 2, 3]
{{5, 1}}, // 1: minimum = 1, maximum = 5
{{0, 3}}, // 2: ["one", "two", "three"]
{{3, 3}}, // 3: pattern "foo+"
};
const char* kRequired[] = {"String"};
const int kIntEnums[] = {1, 2, 3};
const char* kStringEnums[] = {
"one", // 0
"two", // 1
"three", // 2
"foo+", // 3
};
const internal::SchemaData kData = {
kSchemas, kPropertyNodes, kProperties, kRestriction,
kRequired, kIntEnums, kStringEnums,
-1 // validation_schema_root_index
};
Schema schema = Schema::Wrap(&kData);
ASSERT_TRUE(schema.valid());
EXPECT_EQ(base::Value::Type::DICT, schema.type());
// Wrapped schemas have no sensitive values.
EXPECT_FALSE(schema.IsSensitiveValue());
struct ExpectedProperties {
const char* key;
base::Value::Type type;
};
auto kExpectedProperties = std::to_array<ExpectedProperties>({
{"Boolean", base::Value::Type::BOOLEAN},
{"DictRequired", base::Value::Type::DICT},
{"Integer", base::Value::Type::INTEGER},
{"List", base::Value::Type::LIST},
{"Number", base::Value::Type::DOUBLE},
{"String", base::Value::Type::STRING},
{"IntEnum", base::Value::Type::INTEGER},
{"RangedInt", base::Value::Type::INTEGER},
{"StrEnum", base::Value::Type::STRING},
{"StrPat", base::Value::Type::STRING},
});
Schema::Iterator it = schema.GetPropertiesIterator();
for (size_t i = 0; i < std::size(kExpectedProperties); ++i) {
ASSERT_FALSE(it.IsAtEnd());
EXPECT_STREQ(kExpectedProperties[i].key, it.key());
Schema sub = it.schema();
ASSERT_TRUE(sub.valid());
EXPECT_EQ(kExpectedProperties[i].type, sub.type());
if (sub.type() == base::Value::Type::LIST) {
Schema items = sub.GetItems();
ASSERT_TRUE(items.valid());
EXPECT_EQ(base::Value::Type::STRING, items.type());
}
it.Advance();
}
EXPECT_TRUE(it.IsAtEnd());
Schema sub = schema.GetAdditionalProperties();
ASSERT_TRUE(sub.valid());
ASSERT_EQ(base::Value::Type::LIST, sub.type());
Schema subsub = sub.GetItems();
ASSERT_TRUE(subsub.valid());
ASSERT_EQ(base::Value::Type::LIST, subsub.type());
Schema subsubsub = subsub.GetItems();
ASSERT_TRUE(subsubsub.valid());
ASSERT_EQ(base::Value::Type::STRING, subsubsub.type());
SchemaList schema_list = schema.GetPatternProperties("barr");
ASSERT_EQ(1u, schema_list.size());
sub = schema_list[0];
ASSERT_TRUE(sub.valid());
EXPECT_EQ(base::Value::Type::STRING, sub.type());
EXPECT_TRUE(schema.GetPatternProperties("ba").empty());
EXPECT_TRUE(schema.GetPatternProperties("bar+$").empty());
Schema dict = schema.GetKnownProperty("DictRequired");
ASSERT_TRUE(dict.valid());
ASSERT_EQ(base::Value::Type::DICT, dict.type());
EXPECT_EQ(std::vector<std::string>({"String"}), dict.GetRequiredProperties());
}
TEST(SchemaTest, Validate) {
const auto schema = Schema::Parse(kTestSchema);
ASSERT_TRUE(schema.has_value()) << schema.error();
base::Value bundle((base::Value::Dict()));
base::Value::Dict& dict = bundle.GetDict();
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, true);
// Wrong type, expected integer.
dict.Set("Integer", true);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
// Wrong type, expected list of strings.
{
dict.clear();
base::Value::List list;
list.Append(1);
dict.Set("Array", std::move(list));
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
}
// Wrong type in a sub-object.
{
dict.clear();
base::Value::Dict subdict;
subdict.Set("one", "one");
dict.Set("Object", std::move(subdict));
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
}
// Unknown name.
dict.clear();
dict.Set("Unknown", true);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
// All of these will be valid.
dict.clear();
dict.Set("Boolean", true);
dict.Set("Integer", 123);
dict.Set("Number", 3.14);
dict.Set("String", "omg");
{
base::Value::List list;
list.Append("a string");
list.Append("another string");
dict.Set("Array", std::move(list));
}
{
base::Value::Dict subdict;
subdict.Set("one", "string");
subdict.Set("two", 2);
base::Value::List list;
list.Append(subdict.Clone());
list.Append(std::move(subdict));
dict.Set("ArrayOfObjects", std::move(list));
}
{
base::Value::List list;
list.Append("a string");
list.Append("another string");
base::Value::List listlist;
listlist.Append(list.Clone());
listlist.Append(std::move(list));
dict.Set("ArrayOfArray", std::move(listlist));
}
{
base::Value::Dict subdict;
subdict.Set("one", true);
subdict.Set("two", 2);
subdict.Set("additionally", "a string");
subdict.Set("and also", "another string");
dict.Set("Object", std::move(subdict));
}
{
base::Value::Dict subdict;
subdict.Set("Integer", 1);
subdict.Set("String", "a string");
subdict.Set("Number", 3.14);
dict.Set("ObjectWithRequiredProperties", std::move(subdict));
}
dict.Set("IntegerWithEnums", 1);
dict.Set("IntegerWithEnumsGaps", 20);
dict.Set("StringWithEnums", "two");
dict.Set("IntegerWithRange", 3);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, true);
dict.Set("IntegerWithEnums", 0);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithEnums", 1);
dict.Set("IntegerWithEnumsGaps", 0);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithEnumsGaps", 9);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithEnumsGaps", 10);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, true);
dict.Set("IntegerWithEnumsGaps", 11);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithEnumsGaps", 19);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithEnumsGaps", 21);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithEnumsGaps", 29);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithEnumsGaps", 30);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, true);
dict.Set("IntegerWithEnumsGaps", 31);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithEnumsGaps", 100);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithEnumsGaps", 20);
dict.Set("StringWithEnums", "FOUR");
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("StringWithEnums", "two");
dict.Set("IntegerWithRange", 4);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
dict.Set("IntegerWithRange", 3);
// Unknown top level property.
dict.Set("boom", "bang");
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
TestSchemaValidation(*schema, bundle, SCHEMA_ALLOW_UNKNOWN, true);
TestSchemaValidation(*schema, bundle,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
TestSchemaValidation(*schema, bundle, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
true);
TestSchemaValidationWithPath(*schema, bundle, {});
dict.Remove("boom");
// Invalid top level property.
dict.Set("Boolean", 12345);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, false);
TestSchemaValidation(*schema, bundle, SCHEMA_ALLOW_UNKNOWN, false);
TestSchemaValidation(*schema, bundle,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
TestSchemaValidation(*schema, bundle, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
false);
TestSchemaValidationWithPath(*schema, bundle, {"Boolean"});
dict.Set("Boolean", true);
// Tests on ObjectOfObject.
{
Schema subschema = schema->GetProperty("ObjectOfObject");
ASSERT_TRUE(subschema.valid());
base::Value root((base::Value::Dict()));
base::Value::Dict& root_dict = root.GetDict();
// Unknown property.
root_dict.SetByDottedPath("Object.three", false);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
true);
TestSchemaValidationWithPath(subschema, root, {"Object"});
root_dict.RemoveByDottedPath("Object.three");
// Invalid property.
root_dict.SetByDottedPath("Object.one", 12345);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
false);
TestSchemaValidationWithPath(subschema, root, {"Object", "one"});
root_dict.RemoveByDottedPath("Object.one");
}
// Tests on ArrayOfObjects.
{
Schema subschema = schema->GetProperty("ArrayOfObjects");
ASSERT_TRUE(subschema.valid());
base::Value root(base::Value::Type::LIST);
base::Value::List& root_list = root.GetList();
// Unknown property.
base::Value::Dict dict1;
dict1.Set("three", true);
root_list.Append(std::move(dict1));
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
true);
TestSchemaValidationWithPath(subschema, root, {0});
root_list.erase(root_list.end() - 1);
// Invalid property.
base::Value::Dict dict2;
dict2.Set("two", true);
root_list.Append(std::move(dict2));
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
false);
TestSchemaValidationWithPath(subschema, root, {0, "two"});
}
// Tests on ObjectOfArray.
{
Schema subschema = schema->GetProperty("ObjectOfArray");
ASSERT_TRUE(subschema.valid());
base::Value root((base::Value::Dict()));
base::Value::List& list_value =
root.GetDict().Set("List", base::Value::List())->GetList();
// Test that there are not errors here.
list_value.Append(12345);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
true);
// Invalid list item.
list_value.Append("blabla");
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
false);
TestSchemaValidationWithPath(subschema, root, {"List", 1});
}
// Tests on ArrayOfObjectOfArray.
{
Schema subschema = schema->GetProperty("ArrayOfObjectOfArray");
ASSERT_TRUE(subschema.valid());
base::Value root{base::Value::List()};
base::Value::Dict dict_value;
base::Value* list_value = dict_value.Set("List", base::Value::List());
root.GetList().Append(std::move(dict_value));
// Test that there are not errors here.
list_value->GetList().Append("blabla");
TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
true);
// Invalid list item.
list_value->GetList().Append(12345);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
false);
TestSchemaValidationWithPath(subschema, root, {0, "List", 1});
}
// Tests on StringWithPattern.
{
Schema subschema = schema->GetProperty("StringWithPattern");
ASSERT_TRUE(subschema.valid());
TestSchemaValidation(subschema, base::Value("foobar"), SCHEMA_STRICT,
false);
TestSchemaValidation(subschema, base::Value("foo"), SCHEMA_STRICT, true);
TestSchemaValidation(subschema, base::Value("fo"), SCHEMA_STRICT, false);
TestSchemaValidation(subschema, base::Value("fooo"), SCHEMA_STRICT, true);
TestSchemaValidation(subschema, base::Value("^foo+$"), SCHEMA_STRICT,
false);
}
// Tests on ObjectWithPatternProperties.
{
Schema subschema = schema->GetProperty("ObjectWithPatternProperties");
ASSERT_TRUE(subschema.valid());
base::Value root((base::Value::Dict()));
base::Value::Dict& root_dict = root.GetDict();
ASSERT_EQ(1u, subschema.GetPatternProperties("fooo").size());
ASSERT_EQ(1u, subschema.GetPatternProperties("foo").size());
ASSERT_EQ(1u, subschema.GetPatternProperties("barr").size());
ASSERT_EQ(1u, subschema.GetPatternProperties("bar").size());
ASSERT_EQ(1u, subschema.GetMatchingProperties("fooo").size());
ASSERT_EQ(1u, subschema.GetMatchingProperties("foo").size());
ASSERT_EQ(1u, subschema.GetMatchingProperties("barr").size());
ASSERT_EQ(2u, subschema.GetMatchingProperties("bar").size());
ASSERT_TRUE(subschema.GetPatternProperties("foobar").empty());
root_dict.Set("fooo", 123);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
root_dict.Set("fooo", false);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
root_dict.Remove("fooo");
root_dict.Set("foo", 123);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
root_dict.Set("foo", false);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
root_dict.Remove("foo");
root_dict.Set("barr", "one");
TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
root_dict.Set("barr", "three");
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
root_dict.Set("barr", false);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
root_dict.Remove("barr");
root_dict.Set("bar", "one");
TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
root_dict.Set("bar", "two");
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
root_dict.Set("bar", "three");
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
root_dict.Remove("bar");
root_dict.Set("foobar", 123);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, true);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
true);
root_dict.Remove("foobar");
}
// Tests on ObjectWithRequiredProperties
{
Schema subschema = schema->GetProperty("ObjectWithRequiredProperties");
ASSERT_TRUE(subschema.valid());
base::Value root((base::Value::Dict()));
base::Value::Dict& root_dict = root.GetDict();
// Required property missing.
root_dict.Set("Integer", 1);
root_dict.Set("Number", 3.14);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
false);
// Invalid required property.
root_dict.Set("String", 123);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
false);
root_dict.Set("String", "a string");
// Invalid subschema of required property with multiple subschemas.
//
// The "Integer" property has two subschemas, one in "properties" and one
// in "patternProperties". The first test generates a valid schema for the
// first subschema and the second test generates a valid schema for the
// second subschema. In both cases validation should fail because one of the
// required properties is invalid.
root_dict.Set("Integer", 2);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
false);
root_dict.Set("Integer", 3);
TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
TestSchemaValidation(subschema, root,
SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY, false);
TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_WITHOUT_WARNING,
false);
}
// Test that integer to double promotion is allowed.
dict.Set("Number", 31415);
TestSchemaValidation(*schema, bundle, SCHEMA_STRICT, true);
}
TEST(SchemaTest, InvalidReferences) {
// References to undeclared schemas fail.
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"properties": {
"name": { "$ref": "undeclared" }
}
})"));
// Can't refer to self.
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"properties": {
"name": {
"id": "self",
"$ref": "self"
}
}
})"));
// Duplicated IDs are invalid.
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"properties": {
"name": {
"id": "x",
"type": "string"
},
"another": {
"id": "x",
"type": "string"
}
}
})"));
// Main object can't be a reference.
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"id": "main",
"$ref": "main"
})"));
EXPECT_TRUE(ParseFails(R"({
"type": "object",
"$ref": "main"
})"));
}
TEST(SchemaTest, RecursiveReferences) {
// Verifies that references can go to a parent schema, to define a
// recursive type.
const auto schema = Schema::Parse(R"({
"type": "object",
"properties": {
"bookmarks": {
"type": "array",
"id": "ListOfBookmarks",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"url": { "type": "string" },
"children": { "$ref": "ListOfBookmarks" }
}
}
}
}
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
Schema parent = schema->GetKnownProperty("bookmarks");
ASSERT_TRUE(parent.valid());
ASSERT_EQ(base::Value::Type::LIST, parent.type());
// Check the recursive type a number of times.
for (int i = 0; i < 10; ++i) {
Schema items = parent.GetItems();
ASSERT_TRUE(items.valid());
ASSERT_EQ(base::Value::Type::DICT, items.type());
Schema prop = items.GetKnownProperty("name");
ASSERT_TRUE(prop.valid());
ASSERT_EQ(base::Value::Type::STRING, prop.type());
prop = items.GetKnownProperty("url");
ASSERT_TRUE(prop.valid());
ASSERT_EQ(base::Value::Type::STRING, prop.type());
prop = items.GetKnownProperty("children");
ASSERT_TRUE(prop.valid());
ASSERT_EQ(base::Value::Type::LIST, prop.type());
parent = prop;
}
}
TEST(SchemaTest, UnorderedReferences) {
// Verifies that references and IDs can come in any order.
const auto schema = Schema::Parse(R"({
"type": "object",
"properties": {
"a": { "$ref": "shared" },
"b": { "$ref": "shared" },
"c": { "$ref": "shared" },
"d": { "$ref": "shared" },
"e": {
"type": "boolean",
"id": "shared"
},
"f": { "$ref": "shared" },
"g": { "$ref": "shared" },
"h": { "$ref": "shared" },
"i": { "$ref": "shared" }
}
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
for (char c = 'a'; c <= 'i'; ++c) {
Schema sub = schema->GetKnownProperty(std::string(1, c));
ASSERT_TRUE(sub.valid()) << c;
ASSERT_EQ(base::Value::Type::BOOLEAN, sub.type()) << c;
}
}
TEST(SchemaTest, AdditionalPropertiesReference) {
// Verifies that "additionalProperties" can be a reference.
const auto schema = Schema::Parse(R"({
"type": "object",
"properties": {
"policy": {
"type": "object",
"properties": {
"foo": {
"type": "boolean",
"id": "FooId"
}
},
"additionalProperties": { "$ref": "FooId" }
}
}
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
Schema policy = schema->GetKnownProperty("policy");
ASSERT_TRUE(policy.valid());
ASSERT_EQ(base::Value::Type::DICT, policy.type());
Schema foo = policy.GetKnownProperty("foo");
ASSERT_TRUE(foo.valid());
EXPECT_EQ(base::Value::Type::BOOLEAN, foo.type());
Schema additional = policy.GetAdditionalProperties();
ASSERT_TRUE(additional.valid());
EXPECT_EQ(base::Value::Type::BOOLEAN, additional.type());
Schema x = policy.GetProperty("x");
ASSERT_TRUE(x.valid());
EXPECT_EQ(base::Value::Type::BOOLEAN, x.type());
}
TEST(SchemaTest, ItemsReference) {
// Verifies that "items" can be a reference.
const auto schema = Schema::Parse(R"({
"type": "object",
"properties": {
"foo": {
"type": "boolean",
"id": "FooId"
},
"list": {
"type": "array",
"items": { "$ref": "FooId" }
}
}
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
Schema foo = schema->GetKnownProperty("foo");
ASSERT_TRUE(foo.valid());
EXPECT_EQ(base::Value::Type::BOOLEAN, foo.type());
Schema list = schema->GetKnownProperty("list");
ASSERT_TRUE(list.valid());
ASSERT_EQ(base::Value::Type::LIST, list.type());
Schema items = list.GetItems();
ASSERT_TRUE(items.valid());
ASSERT_EQ(base::Value::Type::BOOLEAN, items.type());
}
TEST(SchemaTest, SchemaNodeSensitiveValues) {
const std::string kNormalBooleanSchema = "normal_boolean";
const std::string kSensitiveBooleanSchema = "sensitive_boolean";
const std::string kSensitiveStringSchema = "sensitive_string";
const std::string kSensitiveObjectSchema = "sensitive_object";
const std::string kSensitiveArraySchema = "sensitive_array";
const std::string kSensitiveIntegerSchema = "sensitive_integer";
const std::string kSensitiveNumberSchema = "sensitive_number";
const auto schema = Schema::Parse(R"({
"type": "object",
"properties": {
"normal_boolean": {
"type": "boolean"
},
"sensitive_boolean": {
"type": "boolean",
"sensitiveValue": true
},
"sensitive_string": {
"type": "string",
"sensitiveValue": true
},
"sensitive_object": {
"type": "object",
"additionalProperties": {
"type": "boolean"
},
"sensitiveValue": true
},
"sensitive_array": {
"type": "array",
"items": {
"type": "boolean"
},
"sensitiveValue": true
},
"sensitive_integer": {
"type": "integer",
"sensitiveValue": true
},
"sensitive_number": {
"type": "number",
"sensitiveValue": true
}
}
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
EXPECT_FALSE(schema->IsSensitiveValue());
EXPECT_TRUE(schema->HasSensitiveChildren());
Schema normal_boolean = schema->GetKnownProperty(kNormalBooleanSchema);
ASSERT_TRUE(normal_boolean.valid());
EXPECT_EQ(base::Value::Type::BOOLEAN, normal_boolean.type());
EXPECT_FALSE(normal_boolean.IsSensitiveValue());
EXPECT_FALSE(normal_boolean.HasSensitiveChildren());
Schema sensitive_boolean = schema->GetKnownProperty(kSensitiveBooleanSchema);
ASSERT_TRUE(sensitive_boolean.valid());
EXPECT_EQ(base::Value::Type::BOOLEAN, sensitive_boolean.type());
EXPECT_TRUE(sensitive_boolean.IsSensitiveValue());
EXPECT_FALSE(sensitive_boolean.HasSensitiveChildren());
Schema sensitive_string = schema->GetKnownProperty(kSensitiveStringSchema);
ASSERT_TRUE(sensitive_string.valid());
EXPECT_EQ(base::Value::Type::STRING, sensitive_string.type());
EXPECT_TRUE(sensitive_string.IsSensitiveValue());
EXPECT_FALSE(sensitive_string.HasSensitiveChildren());
Schema sensitive_object = schema->GetKnownProperty(kSensitiveObjectSchema);
ASSERT_TRUE(sensitive_object.valid());
EXPECT_EQ(base::Value::Type::DICT, sensitive_object.type());
EXPECT_TRUE(sensitive_object.IsSensitiveValue());
EXPECT_FALSE(sensitive_object.HasSensitiveChildren());
Schema sensitive_array = schema->GetKnownProperty(kSensitiveArraySchema);
ASSERT_TRUE(sensitive_array.valid());
EXPECT_EQ(base::Value::Type::LIST, sensitive_array.type());
EXPECT_TRUE(sensitive_array.IsSensitiveValue());
EXPECT_FALSE(sensitive_array.HasSensitiveChildren());
Schema sensitive_integer = schema->GetKnownProperty(kSensitiveIntegerSchema);
ASSERT_TRUE(sensitive_integer.valid());
EXPECT_EQ(base::Value::Type::INTEGER, sensitive_integer.type());
EXPECT_TRUE(sensitive_integer.IsSensitiveValue());
EXPECT_FALSE(sensitive_integer.HasSensitiveChildren());
Schema sensitive_number = schema->GetKnownProperty(kSensitiveNumberSchema);
ASSERT_TRUE(sensitive_number.valid());
EXPECT_EQ(base::Value::Type::DOUBLE, sensitive_number.type());
EXPECT_TRUE(sensitive_number.IsSensitiveValue());
EXPECT_FALSE(sensitive_number.HasSensitiveChildren());
// Run |MaskSensitiveValues| on the top-level schema
base::Value::Dict object;
object.Set("objectProperty", true);
base::Value::List array;
array.Append(true);
base::Value value((base::Value::Dict()));
base::Value::Dict& value_dict = value.GetDict();
value_dict.Set(kNormalBooleanSchema, true);
value_dict.Set(kSensitiveBooleanSchema, true);
value_dict.Set(kSensitiveStringSchema, "testvalue");
value_dict.Set(kSensitiveObjectSchema, std::move(object));
value_dict.Set(kSensitiveArraySchema, std::move(array));
value_dict.Set(kSensitiveIntegerSchema, 42);
value_dict.Set(kSensitiveNumberSchema, 3.141);
schema->MaskSensitiveValues(&value);
base::Value value_masked("********");
base::Value::Dict value_expected;
value_expected.Set(kNormalBooleanSchema, true);
value_expected.Set(kSensitiveBooleanSchema, value_masked.Clone());
value_expected.Set(kSensitiveStringSchema, value_masked.Clone());
value_expected.Set(kSensitiveObjectSchema, value_masked.Clone());
value_expected.Set(kSensitiveArraySchema, value_masked.Clone());
value_expected.Set(kSensitiveIntegerSchema, value_masked.Clone());
value_expected.Set(kSensitiveNumberSchema, value_masked.Clone());
EXPECT_EQ(value_expected, value_dict);
// Run |MaskSensitiveValues| on a sub-schema
base::Value string_value("testvalue");
sensitive_string.MaskSensitiveValues(&string_value);
EXPECT_EQ(value_masked.Clone(), string_value);
}
TEST(SchemaTest, SchemaNodeNoSensitiveValues) {
const auto schema = Schema::Parse(R"({
"type": "object",
"properties": {
"foo": {
"type": "boolean"
}
}
})");
ASSERT_TRUE(schema.has_value()) << schema.error();
ASSERT_EQ(base::Value::Type::DICT, schema->type());
EXPECT_FALSE(schema->IsSensitiveValue());
Schema foo = schema->GetKnownProperty("foo");
ASSERT_TRUE(foo.valid());
EXPECT_EQ(base::Value::Type::BOOLEAN, foo.type());
EXPECT_FALSE(foo.IsSensitiveValue());
base::Value value(base::Value::Type::DICT);
value.GetDict().Set("foo", true);
base::Value expected_value = value.Clone();
schema->MaskSensitiveValues(&value);
EXPECT_EQ(expected_value, value);
}
TEST(SchemaTest, EnumerationRestriction) {
// Enum attribute is a list.
EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
"type": "string",
"enum": 12
})")));
// Empty enum attributes is not allowed.
EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
"type": "integer",
"enum": []
})")));
// Enum elements type should be same as stated.
EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
"type": "string",
"enum": [1, 2, 3]
})")));
EXPECT_FALSE(ParseFails(SchemaObjectWrapper(R"({
"type": "integer",
"enum": [1, 2, 3]
})")));
EXPECT_FALSE(ParseFails(SchemaObjectWrapper(R"({
"type": "string",
"enum": ["1", "2", "3"]
})")));
}
TEST(SchemaTest, RangedRestriction) {
EXPECT_TRUE(ParseFails(SchemaObjectWrapper(R"({
"type": "integer",
"minimum": 10,
"maximum": 5
})")));
EXPECT_FALSE(ParseFails(SchemaObjectWrapper(R"({
"type": "integer",
"minimum": 10,
"maximum": 20
})")));
}
TEST(SchemaTest, ParseToDictAndValidate) {
EXPECT_FALSE(
Schema::ParseToDictAndValidate("", kSchemaOptionsNone).has_value());
EXPECT_FALSE(
Schema::ParseToDictAndValidate("\0", kSchemaOptionsNone).has_value());
EXPECT_FALSE(
Schema::ParseToDictAndValidate("string", kSchemaOptionsNone).has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(R"("string")", kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(
Schema::ParseToDictAndValidate("[]", kSchemaOptionsNone).has_value());
EXPECT_FALSE(
Schema::ParseToDictAndValidate("{}", kSchemaOptionsNone).has_value());
EXPECT_FALSE(
Schema::ParseToDictAndValidate(R"({ "type": 123 })", kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(R"({ "type": "invalid" })",
kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "object",
"properties": []
})", // Invalid properties type.
kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "string",
"enum": [ {} ]
})", // "enum" dict values must contain "name".
kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "string",
"enum": [ { "name": {} } ]
})", // "enum" name must be a simple value.
kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "array",
"items": [ 123 ],
})", // "items" must contain a schema or schemas.
kSchemaOptionsNone)
.has_value());
EXPECT_TRUE(Schema::ParseToDictAndValidate(R"({ "type": "object" })",
kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({ "type": ["object", "array"] })", kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "array",
"items": [
{ "type": "string" },
{ "type": "integer" }
]
})",
kSchemaOptionsNone)
.has_value());
EXPECT_TRUE(Schema::ParseToDictAndValidate(
R"({
"type": "object",
"properties": {
"string-property": {
"type": "string",
"title": "The String Policy",
"description": "This policy controls the String widget."
},
"integer-property": {
"type": "number"
},
"enum-property": {
"type": "integer",
"enum": [0, 1, 10, 100]
},
"items-property": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": {
"type": "boolean"
}
})",
kSchemaOptionsNone)
.has_value());
EXPECT_TRUE(Schema::ParseToDictAndValidate(
R"#({
"type": "object",
"patternProperties": {
".": { "type": "boolean" },
"foo": { "type": "boolean" },
"^foo$": { "type": "boolean" },
"foo+": { "type": "boolean" },
"foo?": { "type": "boolean" },
"fo{2,4}": { "type": "boolean" },
"(left)|(right)": { "type": "boolean" }
}
})#",
kSchemaOptionsNone)
.has_value());
EXPECT_TRUE(Schema::ParseToDictAndValidate(
R"({
"type": "object",
"unknown attribute": "that should just be ignored"
})",
kSchemaOptionsIgnoreUnknownAttributes)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "object",
"unknown attribute": "that will cause a failure"
})",
kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "object",
"properties": {"foo": {"type": "number"}},
"required": 123
})",
kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "object",
"properties": {"foo": {"type": "number"}},
"required": [ 123 ]
})",
kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "object",
"properties": {"foo": {"type": "number"}},
"required": ["bar"]
})",
kSchemaOptionsNone)
.has_value());
EXPECT_FALSE(Schema::ParseToDictAndValidate(
R"({
"type": "object",
"required": ["bar"]
})",
kSchemaOptionsNone)
.has_value());
EXPECT_TRUE(Schema::ParseToDictAndValidate(
R"({
"type": "object",
"properties": {"foo": {"type": "number"}},
"required": ["foo"]
})",
kSchemaOptionsNone)
.has_value());
}
TEST(SchemaTest, ErrorPathToString) {
EXPECT_EQ(ErrorPathToString("Policy", {}), "");
EXPECT_EQ(ErrorPathToString("Policy", {"key"}), "Policy.key");
EXPECT_EQ(ErrorPathToString("Policy", {0}), "Policy[0]");
EXPECT_EQ(ErrorPathToString("", {"key"}), ".key");
EXPECT_EQ(ErrorPathToString("", {0}), "[0]");
EXPECT_EQ(ErrorPathToString("Policy", {"key", "key", "key"}),
"Policy.key.key.key");
EXPECT_EQ(ErrorPathToString("Policy", {"a", "b", "c"}), "Policy.a.b.c");
EXPECT_EQ(ErrorPathToString("Policy", {0, 0, 0}), "Policy[0][0][0]");
EXPECT_EQ(ErrorPathToString("Policy", {1, 2, 3}), "Policy[1][2][3]");
EXPECT_EQ(ErrorPathToString("Policy", {0, "key", 5, 7, "example"}),
"Policy[0].key[5][7].example");
}
} // namespace policy