summaryrefslogtreecommitdiff
path: root/components/policy/core/common/schema_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'components/policy/core/common/schema_unittest.cc')
-rw-r--r--components/policy/core/common/schema_unittest.cc1286
1 files changed, 1286 insertions, 0 deletions
diff --git a/components/policy/core/common/schema_unittest.cc b/components/policy/core/common/schema_unittest.cc
new file mode 100644
index 0000000000..4e861d126c
--- /dev/null
+++ b/components/policy/core/common/schema_unittest.cc
@@ -0,0 +1,1286 @@
+// 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/schema.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.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" },
+ "Null": { "type": "null" },
+ "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) {
+ std::string error;
+ Schema schema = Schema::Parse(content, &error);
+ if (schema.valid())
+ return false;
+ EXPECT_FALSE(error.empty());
+ return true;
+}
+
+void TestSchemaValidationHelper(const std::string& source,
+ 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;
+ std::unique_ptr<base::Value> cloned_value(value.DeepCopy());
+ bool touched = false;
+ returned =
+ schema.Normalize(cloned_value.get(), strategy, nullptr, &error, &touched);
+ EXPECT_EQ(expected_return_value, returned) << source << ": " << error;
+
+ 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.get(), SCHEMA_STRICT, nullptr,
+ &error, nullptr))
+ << source;
+ }
+}
+
+void TestSchemaValidationWithPath(Schema schema,
+ const base::Value& value,
+ const std::string& expected_failure_path) {
+ std::string error_path = "NOT_SET";
+ std::string error;
+
+ bool returned = schema.Validate(value, SCHEMA_STRICT, &error_path, &error);
+ ASSERT_FALSE(returned) << error_path;
+ 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) {
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "sub": {
+ "type": "object",
+ "properties": {
+ "subsub": { "type": "string" }
+ }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ schema = schema.GetKnownProperty("sub");
+ ASSERT_TRUE(schema.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ {
+ Schema::Iterator it = schema.GetPropertiesIterator();
+ ASSERT_FALSE(it.IsAtEnd());
+ EXPECT_STREQ("subsub", it.key());
+
+ schema = it.schema();
+ it.Advance();
+ EXPECT_TRUE(it.IsAtEnd());
+ }
+
+ ASSERT_TRUE(schema.valid());
+ EXPECT_EQ(base::Value::Type::STRING, schema.type());
+
+ // This test shouldn't leak nor use invalid memory.
+}
+
+TEST(SchemaTest, ValidSchema) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ ASSERT_EQ(base::Value::Type::DICTIONARY, 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("Null");
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(base::Value::Type::NONE, 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::DICTIONARY, 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::DICTIONARY, 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::DICTIONARY, sub.type());
+
+ sub = schema.GetProperty("ObjectWithRequiredProperties");
+ ASSERT_TRUE(sub.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, 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 },
+ { "Null", base::Value::Type::NONE },
+ { "Number", base::Value::Type::DOUBLE },
+ { "Object", base::Value::Type::DICTIONARY },
+ { "ObjectOfArray", base::Value::Type::DICTIONARY },
+ { "ObjectOfObject", base::Value::Type::DICTIONARY },
+ { "ObjectWithPatternProperties", base::Value::Type::DICTIONARY },
+ { "ObjectWithRequiredProperties", base::Value::Type::DICTIONARY },
+ { "String", base::Value::Type::STRING },
+ { "StringWithEnums", base::Value::Type::STRING },
+ { "StringWithPattern", base::Value::Type::STRING },
+ };
+ Schema::Iterator it = schema.GetPropertiesIterator();
+ for (size_t i = 0; i < arraysize(kExpectedProperties); ++i) {
+ ASSERT_FALSE(it.IsAtEnd());
+ EXPECT_STREQ(kExpectedProperties[i].expected_key, it.key());
+ ASSERT_TRUE(it.schema().valid());
+ EXPECT_EQ(kExpectedProperties[i].expected_type, it.schema().type());
+ it.Advance();
+ }
+ EXPECT_TRUE(it.IsAtEnd());
+}
+
+TEST(SchemaTest, Lookups) {
+ std::string error;
+
+ Schema schema = Schema::Parse(R"({ "type": "object" })", &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ // This empty schema should never find named properties.
+ EXPECT_FALSE(schema.GetKnownProperty("").valid());
+ EXPECT_FALSE(schema.GetKnownProperty("xyz").valid());
+ EXPECT_TRUE(schema.GetPropertiesIterator().IsAtEnd());
+
+ schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "Boolean": { "type": "boolean" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_FALSE(schema.GetKnownProperty("").valid());
+ EXPECT_FALSE(schema.GetKnownProperty("xyz").valid());
+ EXPECT_TRUE(schema.GetKnownProperty("Boolean").valid());
+
+ schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "bb" : { "type": "null" },
+ "aa" : { "type": "boolean" },
+ "abab" : { "type": "string" },
+ "ab" : { "type": "number" },
+ "aba" : { "type": "integer" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, 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 },
+ { "bb", base::Value::Type::NONE },
+ };
+ for (size_t i = 0; i < arraysize(kExpectedKeys); ++i) {
+ Schema sub = schema.GetKnownProperty(kExpectedKeys[i].expected_key);
+ ASSERT_TRUE(sub.valid());
+ EXPECT_EQ(kExpectedKeys[i].expected_type, sub.type());
+ }
+
+ 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"]
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_EQ(std::vector<std::string>({"String", "Object"}),
+ schema.GetRequiredProperties());
+
+ schema = schema.GetKnownProperty("Object");
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ EXPECT_EQ(std::vector<std::string>({"Integer"}),
+ schema.GetRequiredProperties());
+}
+
+TEST(SchemaTest, Wrap) {
+ const internal::SchemaNode kSchemas[] = {
+ { base::Value::Type::DICTIONARY, 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::DICTIONARY, 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,
+ };
+
+ Schema schema = Schema::Wrap(&kData);
+ ASSERT_TRUE(schema.valid());
+ EXPECT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ struct {
+ const char* key;
+ base::Value::Type type;
+ } kExpectedProperties[] = {
+ { "Boolean", base::Value::Type::BOOLEAN },
+ { "DictRequired", base::Value::Type::DICTIONARY },
+ { "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 < arraysize(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::DICTIONARY, dict.type());
+
+ EXPECT_EQ(std::vector<std::string>({"String"}), dict.GetRequiredProperties());
+}
+
+TEST(SchemaTest, Validate) {
+ std::string error;
+ Schema schema = Schema::Parse(kTestSchema, &error);
+ ASSERT_TRUE(schema.valid()) << error;
+
+ base::DictionaryValue bundle;
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+
+ // Wrong type, expected integer.
+ bundle.SetBoolean("Integer", true);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+
+ // Wrong type, expected list of strings.
+ {
+ bundle.Clear();
+ base::ListValue list;
+ list.AppendInteger(1);
+ bundle.SetKey("Array", std::move(list));
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ }
+
+ // Wrong type in a sub-object.
+ {
+ bundle.Clear();
+ base::DictionaryValue dict;
+ dict.SetString("one", "one");
+ bundle.SetKey("Object", std::move(dict));
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ }
+
+ // Unknown name.
+ bundle.Clear();
+ bundle.SetBoolean("Unknown", true);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+
+ // All of these will be valid.
+ bundle.Clear();
+ bundle.SetBoolean("Boolean", true);
+ bundle.SetInteger("Integer", 123);
+ bundle.Set("Null", std::make_unique<base::Value>());
+ bundle.SetDouble("Number", 3.14);
+ bundle.SetString("String", "omg");
+
+ {
+ base::ListValue list;
+ list.AppendString("a string");
+ list.AppendString("another string");
+ bundle.SetKey("Array", std::move(list));
+ }
+
+ {
+ base::DictionaryValue dict;
+ dict.SetString("one", "string");
+ dict.SetInteger("two", 2);
+ base::ListValue list;
+ list.GetList().push_back(dict.Clone());
+ list.GetList().push_back(std::move(dict));
+ bundle.SetKey("ArrayOfObjects", std::move(list));
+ }
+
+ {
+ base::ListValue list;
+ list.AppendString("a string");
+ list.AppendString("another string");
+ base::ListValue listlist;
+ listlist.GetList().push_back(list.Clone());
+ listlist.GetList().push_back(std::move(list));
+ bundle.SetKey("ArrayOfArray", std::move(listlist));
+ }
+
+ {
+ base::DictionaryValue dict;
+ dict.SetBoolean("one", true);
+ dict.SetInteger("two", 2);
+ dict.SetString("additionally", "a string");
+ dict.SetString("and also", "another string");
+ bundle.SetKey("Object", std::move(dict));
+ }
+
+ {
+ base::DictionaryValue dict;
+ dict.SetInteger("Integer", 1);
+ dict.SetString("String", "a string");
+ dict.SetDouble("Number", 3.14);
+ bundle.SetKey("ObjectWithRequiredProperties", std::move(dict));
+ }
+
+ bundle.SetInteger("IntegerWithEnums", 1);
+ bundle.SetInteger("IntegerWithEnumsGaps", 20);
+ bundle.SetString("StringWithEnums", "two");
+ bundle.SetInteger("IntegerWithRange", 3);
+
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+
+ bundle.SetInteger("IntegerWithEnums", 0);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnums", 1);
+
+ bundle.SetInteger("IntegerWithEnumsGaps", 0);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 9);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 10);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+ bundle.SetInteger("IntegerWithEnumsGaps", 11);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 19);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 21);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 29);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 30);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, true);
+ bundle.SetInteger("IntegerWithEnumsGaps", 31);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 100);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithEnumsGaps", 20);
+
+ bundle.SetString("StringWithEnums", "FOUR");
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetString("StringWithEnums", "two");
+
+ bundle.SetInteger("IntegerWithRange", 4);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ bundle.SetInteger("IntegerWithRange", 3);
+
+ // Unknown top level property.
+ bundle.SetString("boom", "bang");
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, true);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidationWithPath(schema, bundle, "");
+ bundle.Remove("boom", nullptr);
+
+ // Invalid top level property.
+ bundle.SetInteger("Boolean", 12345);
+ TestSchemaValidation(schema, bundle, SCHEMA_STRICT, false);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(schema, bundle, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(schema, bundle, "Boolean");
+ bundle.SetBoolean("Boolean", true);
+
+ // Tests on ObjectOfObject.
+ {
+ Schema subschema = schema.GetProperty("ObjectOfObject");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ // Unknown property.
+ root.SetBoolean("Object.three", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "Object");
+ root.Remove("Object.three", nullptr);
+
+ // Invalid property.
+ root.SetInteger("Object.one", 12345);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "Object.one");
+ root.Remove("Object.one", nullptr);
+ }
+
+ // Tests on ArrayOfObjects.
+ {
+ Schema subschema = schema.GetProperty("ArrayOfObjects");
+ ASSERT_TRUE(subschema.valid());
+ base::ListValue root;
+
+ // Unknown property.
+ std::unique_ptr<base::DictionaryValue> dict_value(
+ new base::DictionaryValue());
+ dict_value->SetBoolean("three", true);
+ root.Append(std::move(dict_value));
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "items[0]");
+ root.Remove(root.GetSize() - 1, nullptr);
+
+ // Invalid property.
+ dict_value.reset(new base::DictionaryValue());
+ dict_value->SetBoolean("two", true);
+ root.Append(std::move(dict_value));
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "items[0].two");
+ root.Remove(root.GetSize() - 1, nullptr);
+ }
+
+ // Tests on ObjectOfArray.
+ {
+ Schema subschema = schema.GetProperty("ObjectOfArray");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ base::ListValue* list_value =
+ root.SetList("List", std::make_unique<base::ListValue>());
+
+ // Test that there are not errors here.
+ list_value->AppendInteger(12345);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+
+ // Invalid list item.
+ list_value->AppendString("blabla");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "List.items[1]");
+ }
+
+ // Tests on ArrayOfObjectOfArray.
+ {
+ Schema subschema = schema.GetProperty("ArrayOfObjectOfArray");
+ ASSERT_TRUE(subschema.valid());
+ base::ListValue root;
+
+ auto dict_value = std::make_unique<base::DictionaryValue>();
+ base::ListValue* list_value =
+ dict_value->SetList("List", std::make_unique<base::ListValue>());
+ root.Append(std::move(dict_value));
+
+ // Test that there are not errors here.
+ list_value->AppendString("blabla");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+
+ // Invalid list item.
+ list_value->AppendInteger(12345);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, true);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, true);
+ TestSchemaValidationWithPath(subschema, root, "items[0].List.items[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::DictionaryValue root;
+
+ 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.SetInteger("fooo", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetBoolean("fooo", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.Remove("fooo", nullptr);
+
+ root.SetInteger("foo", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetBoolean("foo", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.Remove("foo", nullptr);
+
+ root.SetString("barr", "one");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetString("barr", "three");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.SetBoolean("barr", false);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.Remove("barr", nullptr);
+
+ root.SetString("bar", "one");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, true);
+ root.SetString("bar", "two");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.SetString("bar", "three");
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ root.Remove("bar", nullptr);
+
+ root.SetInteger("foobar", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, true);
+ root.Remove("foobar", nullptr);
+ }
+
+ // Tests on ObjectWithRequiredProperties
+ {
+ Schema subschema = schema.GetProperty("ObjectWithRequiredProperties");
+ ASSERT_TRUE(subschema.valid());
+ base::DictionaryValue root;
+
+ // Required property missing.
+ root.SetInteger("Integer", 1);
+ root.SetDouble("Number", 3.14);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, false);
+
+ // Invalid required property.
+ root.SetInteger("String", 123);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, false);
+ root.SetString("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.SetInteger("Integer", 2);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, false);
+
+ root.SetInteger("Integer", 3);
+ TestSchemaValidation(subschema, root, SCHEMA_STRICT, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_UNKNOWN, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID_TOPLEVEL, false);
+ TestSchemaValidation(subschema, root, SCHEMA_ALLOW_INVALID, false);
+ }
+
+ // Test that integer to double promotion is allowed.
+ bundle.SetInteger("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.
+ std::string error;
+ Schema 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" }
+ }
+ }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, 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::DICTIONARY, 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.
+ std::string error;
+ Schema 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" }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, 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.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "policy": {
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "boolean",
+ "id": "FooId"
+ }
+ },
+ "additionalProperties": { "$ref": "FooId" }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, schema.type());
+
+ Schema policy = schema.GetKnownProperty("policy");
+ ASSERT_TRUE(policy.valid());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, 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.
+ std::string error;
+ Schema schema = Schema::Parse(R"({
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "boolean",
+ "id": "FooId"
+ },
+ "list": {
+ "type": "array",
+ "items": { "$ref": "FooId" }
+ }
+ }
+ })",
+ &error);
+ ASSERT_TRUE(schema.valid()) << error;
+ ASSERT_EQ(base::Value::Type::DICTIONARY, 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, 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
+ })")));
+}
+
+} // namespace policy