| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "extensions/renderer/argument_spec.h" | |
| 6 #include "base/memory/ptr_util.h" | |
| 7 #include "base/values.h" | |
| 8 #include "extensions/renderer/api_binding_test_util.h" | |
| 9 #include "extensions/renderer/api_invocation_errors.h" | |
| 10 #include "extensions/renderer/api_type_reference_map.h" | |
| 11 #include "gin/converter.h" | |
| 12 #include "gin/public/isolate_holder.h" | |
| 13 #include "gin/test/v8_test.h" | |
| 14 #include "testing/gtest/include/gtest/gtest.h" | |
| 15 #include "v8/include/v8.h" | |
| 16 | |
| 17 namespace extensions { | |
| 18 | |
| 19 class ArgumentSpecUnitTest : public gin::V8Test { | |
| 20 protected: | |
| 21 ArgumentSpecUnitTest() | |
| 22 : type_refs_(APITypeReferenceMap::InitializeTypeCallback()) {} | |
| 23 ~ArgumentSpecUnitTest() override {} | |
| 24 | |
| 25 enum class TestResult { | |
| 26 PASS, | |
| 27 FAIL, | |
| 28 THROW, | |
| 29 }; | |
| 30 | |
| 31 struct RunTestParams { | |
| 32 RunTestParams(const ArgumentSpec& spec, | |
| 33 base::StringPiece script_source, | |
| 34 TestResult result) | |
| 35 : spec(spec), script_source(script_source), expected_result(result) {} | |
| 36 | |
| 37 const ArgumentSpec& spec; | |
| 38 base::StringPiece script_source; | |
| 39 TestResult expected_result; | |
| 40 base::StringPiece expected_json; | |
| 41 base::StringPiece expected_error; | |
| 42 base::StringPiece expected_thrown_message; | |
| 43 const base::Value* expected_value = nullptr; | |
| 44 bool should_convert = true; | |
| 45 }; | |
| 46 | |
| 47 void ExpectSuccess(const ArgumentSpec& spec, | |
| 48 const std::string& script_source, | |
| 49 const std::string& expected_json_single_quotes) { | |
| 50 RunTestParams params(spec, script_source, TestResult::PASS); | |
| 51 std::string expected_json = | |
| 52 ReplaceSingleQuotes(expected_json_single_quotes); | |
| 53 params.expected_json = expected_json; | |
| 54 RunTest(params); | |
| 55 } | |
| 56 | |
| 57 void ExpectSuccess(const ArgumentSpec& spec, | |
| 58 const std::string& script_source, | |
| 59 const base::Value& expected_value) { | |
| 60 RunTestParams params(spec, script_source, TestResult::PASS); | |
| 61 params.expected_value = &expected_value; | |
| 62 RunTest(params); | |
| 63 } | |
| 64 | |
| 65 void ExpectSuccessWithNoConversion(const ArgumentSpec& spec, | |
| 66 const std::string& script_source) { | |
| 67 RunTestParams params(spec, script_source, TestResult::PASS); | |
| 68 params.should_convert = false; | |
| 69 RunTest(params); | |
| 70 } | |
| 71 | |
| 72 void ExpectFailure(const ArgumentSpec& spec, | |
| 73 const std::string& script_source, | |
| 74 const std::string& expected_error) { | |
| 75 RunTestParams params(spec, script_source, TestResult::FAIL); | |
| 76 params.expected_error = expected_error; | |
| 77 RunTest(params); | |
| 78 } | |
| 79 | |
| 80 void ExpectFailureWithNoConversion(const ArgumentSpec& spec, | |
| 81 const std::string& script_source, | |
| 82 const std::string& expected_error) { | |
| 83 RunTestParams params(spec, script_source, TestResult::FAIL); | |
| 84 params.should_convert = false; | |
| 85 params.expected_error = expected_error; | |
| 86 RunTest(params); | |
| 87 } | |
| 88 | |
| 89 void ExpectThrow(const ArgumentSpec& spec, | |
| 90 const std::string& script_source, | |
| 91 const std::string& expected_thrown_message) { | |
| 92 RunTestParams params(spec, script_source, TestResult::THROW); | |
| 93 params.expected_thrown_message = expected_thrown_message; | |
| 94 RunTest(params); | |
| 95 } | |
| 96 | |
| 97 void AddTypeRef(const std::string& id, std::unique_ptr<ArgumentSpec> spec) { | |
| 98 type_refs_.AddSpec(id, std::move(spec)); | |
| 99 } | |
| 100 | |
| 101 private: | |
| 102 void RunTest(const RunTestParams& params); | |
| 103 | |
| 104 APITypeReferenceMap type_refs_; | |
| 105 | |
| 106 DISALLOW_COPY_AND_ASSIGN(ArgumentSpecUnitTest); | |
| 107 }; | |
| 108 | |
| 109 void ArgumentSpecUnitTest::RunTest(const RunTestParams& params) { | |
| 110 v8::Isolate* isolate = instance_->isolate(); | |
| 111 v8::HandleScope handle_scope(instance_->isolate()); | |
| 112 | |
| 113 v8::Local<v8::Context> context = | |
| 114 v8::Local<v8::Context>::New(instance_->isolate(), context_); | |
| 115 v8::TryCatch try_catch(isolate); | |
| 116 v8::Local<v8::Value> val = | |
| 117 V8ValueFromScriptSource(context, params.script_source); | |
| 118 ASSERT_FALSE(val.IsEmpty()) << params.script_source; | |
| 119 | |
| 120 std::string error; | |
| 121 std::unique_ptr<base::Value> out_value; | |
| 122 bool did_succeed = params.spec.ParseArgument( | |
| 123 context, val, type_refs_, params.should_convert ? &out_value : nullptr, | |
| 124 &error); | |
| 125 bool should_succeed = params.expected_result == TestResult::PASS; | |
| 126 ASSERT_EQ(should_succeed, did_succeed) | |
| 127 << params.script_source << ", " << error; | |
| 128 ASSERT_EQ(did_succeed && params.should_convert, !!out_value); | |
| 129 bool should_throw = params.expected_result == TestResult::THROW; | |
| 130 ASSERT_EQ(should_throw, try_catch.HasCaught()) << params.script_source; | |
| 131 | |
| 132 if (!params.expected_error.empty()) | |
| 133 EXPECT_EQ(params.expected_error, error) << params.script_source; | |
| 134 | |
| 135 if (should_succeed && params.should_convert) { | |
| 136 ASSERT_TRUE(out_value); | |
| 137 if (params.expected_value) | |
| 138 EXPECT_TRUE(params.expected_value->Equals(out_value.get())) | |
| 139 << params.script_source; | |
| 140 else | |
| 141 EXPECT_EQ(params.expected_json, ValueToString(*out_value)); | |
| 142 } else if (should_throw) { | |
| 143 EXPECT_EQ(params.expected_thrown_message, | |
| 144 gin::V8ToString(try_catch.Message()->Get())); | |
| 145 } | |
| 146 } | |
| 147 | |
| 148 TEST_F(ArgumentSpecUnitTest, Test) { | |
| 149 using namespace api_errors; | |
| 150 | |
| 151 { | |
| 152 ArgumentSpec spec(*ValueFromString("{'type': 'integer'}")); | |
| 153 ExpectSuccess(spec, "1", "1"); | |
| 154 ExpectSuccess(spec, "-1", "-1"); | |
| 155 ExpectSuccess(spec, "0", "0"); | |
| 156 ExpectSuccess(spec, "0.0", "0"); | |
| 157 ExpectFailure(spec, "undefined", InvalidType(kTypeInteger, kTypeUndefined)); | |
| 158 ExpectFailure(spec, "null", InvalidType(kTypeInteger, kTypeNull)); | |
| 159 ExpectFailure(spec, "1.1", InvalidType(kTypeInteger, kTypeDouble)); | |
| 160 ExpectFailure(spec, "'foo'", InvalidType(kTypeInteger, kTypeString)); | |
| 161 ExpectFailure(spec, "'1'", InvalidType(kTypeInteger, kTypeString)); | |
| 162 ExpectFailure(spec, "({})", InvalidType(kTypeInteger, kTypeObject)); | |
| 163 ExpectFailure(spec, "[1]", InvalidType(kTypeInteger, kTypeList)); | |
| 164 } | |
| 165 | |
| 166 { | |
| 167 ArgumentSpec spec(*ValueFromString("{'type': 'number'}")); | |
| 168 ExpectSuccess(spec, "1", "1.0"); | |
| 169 ExpectSuccess(spec, "-1", "-1.0"); | |
| 170 ExpectSuccess(spec, "0", "0.0"); | |
| 171 ExpectSuccess(spec, "1.1", "1.1"); | |
| 172 ExpectSuccess(spec, "1.", "1.0"); | |
| 173 ExpectSuccess(spec, ".1", "0.1"); | |
| 174 ExpectFailure(spec, "undefined", InvalidType(kTypeDouble, kTypeUndefined)); | |
| 175 ExpectFailure(spec, "null", InvalidType(kTypeDouble, kTypeNull)); | |
| 176 ExpectFailure(spec, "'foo'", InvalidType(kTypeDouble, kTypeString)); | |
| 177 ExpectFailure(spec, "'1.1'", InvalidType(kTypeDouble, kTypeString)); | |
| 178 ExpectFailure(spec, "({})", InvalidType(kTypeDouble, kTypeObject)); | |
| 179 ExpectFailure(spec, "[1.1]", InvalidType(kTypeDouble, kTypeList)); | |
| 180 } | |
| 181 | |
| 182 { | |
| 183 ArgumentSpec spec(*ValueFromString("{'type': 'integer', 'minimum': 1}")); | |
| 184 ExpectSuccess(spec, "2", "2"); | |
| 185 ExpectSuccess(spec, "1", "1"); | |
| 186 ExpectFailure(spec, "0", NumberTooSmall(1)); | |
| 187 ExpectFailure(spec, "-1", NumberTooSmall(1)); | |
| 188 } | |
| 189 | |
| 190 { | |
| 191 ArgumentSpec spec(*ValueFromString("{'type': 'integer', 'maximum': 10}")); | |
| 192 ExpectSuccess(spec, "10", "10"); | |
| 193 ExpectSuccess(spec, "1", "1"); | |
| 194 ExpectFailure(spec, "11", NumberTooLarge(10)); | |
| 195 } | |
| 196 | |
| 197 { | |
| 198 ArgumentSpec spec(*ValueFromString("{'type': 'string'}")); | |
| 199 ExpectSuccess(spec, "'foo'", "'foo'"); | |
| 200 ExpectSuccess(spec, "''", "''"); | |
| 201 ExpectFailure(spec, "1", InvalidType(kTypeString, kTypeInteger)); | |
| 202 ExpectFailure(spec, "({})", InvalidType(kTypeString, kTypeObject)); | |
| 203 ExpectFailure(spec, "['foo']", InvalidType(kTypeString, kTypeList)); | |
| 204 } | |
| 205 | |
| 206 { | |
| 207 ArgumentSpec spec( | |
| 208 *ValueFromString("{'type': 'string', 'enum': ['foo', 'bar']}")); | |
| 209 std::set<std::string> valid_enums = {"foo", "bar"}; | |
| 210 ExpectSuccess(spec, "'foo'", "'foo'"); | |
| 211 ExpectSuccess(spec, "'bar'", "'bar'"); | |
| 212 ExpectFailure(spec, "['foo']", InvalidType(kTypeString, kTypeList)); | |
| 213 ExpectFailure(spec, "'fo'", InvalidEnumValue(valid_enums)); | |
| 214 ExpectFailure(spec, "'foobar'", InvalidEnumValue(valid_enums)); | |
| 215 ExpectFailure(spec, "'baz'", InvalidEnumValue(valid_enums)); | |
| 216 ExpectFailure(spec, "''", InvalidEnumValue(valid_enums)); | |
| 217 } | |
| 218 | |
| 219 { | |
| 220 ArgumentSpec spec(*ValueFromString( | |
| 221 "{'type': 'string', 'enum': [{'name': 'foo'}, {'name': 'bar'}]}")); | |
| 222 std::set<std::string> valid_enums = {"foo", "bar"}; | |
| 223 ExpectSuccess(spec, "'foo'", "'foo'"); | |
| 224 ExpectSuccess(spec, "'bar'", "'bar'"); | |
| 225 ExpectFailure(spec, "['foo']", InvalidType(kTypeString, kTypeList)); | |
| 226 ExpectFailure(spec, "'fo'", InvalidEnumValue(valid_enums)); | |
| 227 ExpectFailure(spec, "'foobar'", InvalidEnumValue(valid_enums)); | |
| 228 ExpectFailure(spec, "'baz'", InvalidEnumValue(valid_enums)); | |
| 229 ExpectFailure(spec, "''", InvalidEnumValue(valid_enums)); | |
| 230 } | |
| 231 | |
| 232 { | |
| 233 ArgumentSpec spec(*ValueFromString("{'type': 'boolean'}")); | |
| 234 ExpectSuccess(spec, "true", "true"); | |
| 235 ExpectSuccess(spec, "false", "false"); | |
| 236 ExpectFailure(spec, "1", InvalidType(kTypeBoolean, kTypeInteger)); | |
| 237 ExpectFailure(spec, "'true'", InvalidType(kTypeBoolean, kTypeString)); | |
| 238 ExpectFailure(spec, "null", InvalidType(kTypeBoolean, kTypeNull)); | |
| 239 } | |
| 240 | |
| 241 { | |
| 242 ArgumentSpec spec( | |
| 243 *ValueFromString("{'type': 'array', 'items': {'type': 'string'}}")); | |
| 244 ExpectSuccess(spec, "[]", "[]"); | |
| 245 ExpectSuccess(spec, "['foo']", "['foo']"); | |
| 246 ExpectSuccess(spec, "['foo', 'bar']", "['foo','bar']"); | |
| 247 ExpectSuccess(spec, "var x = new Array(); x[0] = 'foo'; x;", "['foo']"); | |
| 248 ExpectFailure(spec, "'foo'", InvalidType(kTypeList, kTypeString)); | |
| 249 ExpectFailure(spec, "[1, 2]", | |
| 250 IndexError(0u, InvalidType(kTypeString, kTypeInteger))); | |
| 251 ExpectFailure(spec, "['foo', 1]", | |
| 252 IndexError(1u, InvalidType(kTypeString, kTypeInteger))); | |
| 253 ExpectFailure(spec, | |
| 254 "var x = ['a', 'b', 'c'];" | |
| 255 "x[4] = 'd';" // x[3] is undefined, violating the spec. | |
| 256 "x;", | |
| 257 IndexError(3u, InvalidType(kTypeString, kTypeUndefined))); | |
| 258 ExpectThrow( | |
| 259 spec, | |
| 260 "var x = [];" | |
| 261 "Object.defineProperty(" | |
| 262 " x, 0," | |
| 263 " { get: () => { throw new Error('Badness'); } });" | |
| 264 "x;", | |
| 265 "Uncaught Error: Badness"); | |
| 266 } | |
| 267 | |
| 268 { | |
| 269 const char kObjectSpec[] = | |
| 270 "{" | |
| 271 " 'type': 'object'," | |
| 272 " 'properties': {" | |
| 273 " 'prop1': {'type': 'string'}," | |
| 274 " 'prop2': {'type': 'integer', 'optional': true}" | |
| 275 " }" | |
| 276 "}"; | |
| 277 ArgumentSpec spec(*ValueFromString(kObjectSpec)); | |
| 278 ExpectSuccess(spec, "({prop1: 'foo', prop2: 2})", | |
| 279 "{'prop1':'foo','prop2':2}"); | |
| 280 ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}"); | |
| 281 ExpectSuccess(spec, "({prop1: 'foo', prop2: null})", "{'prop1':'foo'}"); | |
| 282 ExpectSuccess(spec, "x = {}; x.prop1 = 'foo'; x;", "{'prop1':'foo'}"); | |
| 283 ExpectFailure( | |
| 284 spec, "({prop1: 'foo', prop2: 'bar'})", | |
| 285 PropertyError("prop2", InvalidType(kTypeInteger, kTypeString))); | |
| 286 ExpectFailure(spec, "({prop2: 2})", MissingRequiredProperty("prop1")); | |
| 287 // Unknown properties are not allowed. | |
| 288 ExpectFailure(spec, "({prop1: 'foo', prop2: 2, prop3: 'blah'})", | |
| 289 UnexpectedProperty("prop3")); | |
| 290 // We only consider properties on the object itself, not its prototype | |
| 291 // chain. | |
| 292 ExpectFailure(spec, | |
| 293 "function X() {}\n" | |
| 294 "X.prototype = { prop1: 'foo' };\n" | |
| 295 "var x = new X();\n" | |
| 296 "x;", | |
| 297 MissingRequiredProperty("prop1")); | |
| 298 ExpectFailure(spec, | |
| 299 "function X() {}\n" | |
| 300 "X.prototype = { prop1: 'foo' };\n" | |
| 301 "function Y() { this.__proto__ = X.prototype; }\n" | |
| 302 "var z = new Y();\n" | |
| 303 "z;", | |
| 304 MissingRequiredProperty("prop1")); | |
| 305 // Self-referential fun. Currently we don't have to worry about these much | |
| 306 // because the spec won't match at some point (and V8ValueConverter has | |
| 307 // cycle detection and will fail). | |
| 308 ExpectFailure( | |
| 309 spec, "x = {}; x.prop1 = x; x;", | |
| 310 PropertyError("prop1", InvalidType(kTypeString, kTypeObject))); | |
| 311 ExpectThrow( | |
| 312 spec, | |
| 313 "({ get prop1() { throw new Error('Badness'); }});", | |
| 314 "Uncaught Error: Badness"); | |
| 315 ExpectThrow(spec, | |
| 316 "x = {prop1: 'foo'};\n" | |
| 317 "Object.defineProperty(\n" | |
| 318 " x, 'prop2',\n" | |
| 319 " {\n" | |
| 320 " get: () => { throw new Error('Badness'); },\n" | |
| 321 " enumerable: true,\n" | |
| 322 "});\n" | |
| 323 "x;", | |
| 324 "Uncaught Error: Badness"); | |
| 325 // By default, properties from Object.defineProperty() aren't enumerable, | |
| 326 // so they will be ignored in our matching. | |
| 327 ExpectSuccess(spec, | |
| 328 "x = {prop1: 'foo'};\n" | |
| 329 "Object.defineProperty(\n" | |
| 330 " x, 'prop2',\n" | |
| 331 " { get: () => { throw new Error('Badness'); } });\n" | |
| 332 "x;", | |
| 333 "{'prop1':'foo'}"); | |
| 334 } | |
| 335 | |
| 336 { | |
| 337 const char kFunctionSpec[] = "{ 'type': 'function' }"; | |
| 338 ArgumentSpec spec(*ValueFromString(kFunctionSpec)); | |
| 339 // Functions are serialized as empty dictionaries. | |
| 340 ExpectSuccess(spec, "(function() {})", "{}"); | |
| 341 ExpectSuccessWithNoConversion(spec, "(function() {})"); | |
| 342 ExpectSuccessWithNoConversion(spec, "(function(a, b) { a(); b(); })"); | |
| 343 ExpectSuccessWithNoConversion(spec, "(function(a, b) { a(); b(); })"); | |
| 344 ExpectFailureWithNoConversion(spec, "({a: function() {}})", | |
| 345 InvalidType(kTypeFunction, kTypeObject)); | |
| 346 ExpectFailureWithNoConversion(spec, "([function() {}])", | |
| 347 InvalidType(kTypeFunction, kTypeList)); | |
| 348 ExpectFailureWithNoConversion(spec, "1", | |
| 349 InvalidType(kTypeFunction, kTypeInteger)); | |
| 350 } | |
| 351 | |
| 352 { | |
| 353 const char kBinarySpec[] = "{ 'type': 'binary' }"; | |
| 354 ArgumentSpec spec(*ValueFromString(kBinarySpec)); | |
| 355 // Simple case: empty ArrayBuffer -> empty BinaryValue. | |
| 356 ExpectSuccess(spec, "(new ArrayBuffer())", | |
| 357 base::Value(base::Value::Type::BINARY)); | |
| 358 { | |
| 359 // A non-empty (but zero-filled) ArrayBufferView. | |
| 360 const char kBuffer[] = {0, 0, 0, 0}; | |
| 361 std::unique_ptr<base::Value> expected_value = | |
| 362 base::Value::CreateWithCopiedBuffer(kBuffer, arraysize(kBuffer)); | |
| 363 ASSERT_TRUE(expected_value); | |
| 364 ExpectSuccessWithNoConversion(spec, "(new Int32Array(2))"); | |
| 365 } | |
| 366 { | |
| 367 // Actual data. | |
| 368 const char kBuffer[] = {'p', 'i', 'n', 'g'}; | |
| 369 std::unique_ptr<base::Value> expected_value = | |
| 370 base::Value::CreateWithCopiedBuffer(kBuffer, arraysize(kBuffer)); | |
| 371 ASSERT_TRUE(expected_value); | |
| 372 ExpectSuccess(spec, | |
| 373 "var b = new ArrayBuffer(4);\n" | |
| 374 "var v = new Uint8Array(b);\n" | |
| 375 "var s = 'ping';\n" | |
| 376 "for (var i = 0; i < s.length; ++i)\n" | |
| 377 " v[i] = s.charCodeAt(i);\n" | |
| 378 "b;", | |
| 379 *expected_value); | |
| 380 } | |
| 381 ExpectFailure(spec, "1", InvalidType(kTypeBinary, kTypeInteger)); | |
| 382 } | |
| 383 { | |
| 384 const char kAnySpec[] = "{ 'type': 'any' }"; | |
| 385 ArgumentSpec spec(*ValueFromString(kAnySpec)); | |
| 386 ExpectSuccess(spec, "42", "42"); | |
| 387 ExpectSuccess(spec, "'foo'", "'foo'"); | |
| 388 ExpectSuccess(spec, "({prop1:'bar'})", "{'prop1':'bar'}"); | |
| 389 ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]"); | |
| 390 ExpectSuccess(spec, "[1, 'a']", "[1,'a']"); | |
| 391 ExpectSuccess(spec, "null", base::Value()); | |
| 392 ExpectSuccess(spec, "({prop1: 'alpha', prop2: null})", "{'prop1':'alpha'}"); | |
| 393 ExpectSuccess(spec, | |
| 394 "x = {alpha: 'alpha'};\n" | |
| 395 "y = {beta: 'beta', x: x};\n" | |
| 396 "y;", | |
| 397 "{'beta':'beta','x':{'alpha':'alpha'}}"); | |
| 398 // We don't serialize undefined. | |
| 399 // TODO(devlin): This matches current behavior, but should it? Part of the | |
| 400 // problem is that base::Values don't differentiate between undefined and | |
| 401 // null, which is a potentially important distinction. However, this means | |
| 402 // that in serialization of an object {a: 1, foo:undefined}, we lose the | |
| 403 // 'foo' property. | |
| 404 ExpectFailure(spec, "undefined", UnserializableValue()); | |
| 405 | |
| 406 ExpectSuccess(spec, "({prop1: 1, prop2: undefined})", "{'prop1':1}"); | |
| 407 } | |
| 408 } | |
| 409 | |
| 410 TEST_F(ArgumentSpecUnitTest, TypeRefsTest) { | |
| 411 using namespace api_errors; | |
| 412 const char kObjectType[] = | |
| 413 "{" | |
| 414 " 'id': 'refObj'," | |
| 415 " 'type': 'object'," | |
| 416 " 'properties': {" | |
| 417 " 'prop1': {'type': 'string'}," | |
| 418 " 'prop2': {'type': 'integer', 'optional': true}" | |
| 419 " }" | |
| 420 "}"; | |
| 421 const char kEnumType[] = | |
| 422 "{'id': 'refEnum', 'type': 'string', 'enum': ['alpha', 'beta']}"; | |
| 423 AddTypeRef("refObj", | |
| 424 base::MakeUnique<ArgumentSpec>(*ValueFromString(kObjectType))); | |
| 425 AddTypeRef("refEnum", | |
| 426 base::MakeUnique<ArgumentSpec>(*ValueFromString(kEnumType))); | |
| 427 std::set<std::string> valid_enums = {"alpha", "beta"}; | |
| 428 | |
| 429 { | |
| 430 const char kObjectWithRefEnumSpec[] = | |
| 431 "{" | |
| 432 " 'name': 'objWithRefEnum'," | |
| 433 " 'type': 'object'," | |
| 434 " 'properties': {" | |
| 435 " 'e': {'$ref': 'refEnum'}," | |
| 436 " 'sub': {'type': 'integer'}" | |
| 437 " }" | |
| 438 "}"; | |
| 439 ArgumentSpec spec(*ValueFromString(kObjectWithRefEnumSpec)); | |
| 440 ExpectSuccess(spec, "({e: 'alpha', sub: 1})", "{'e':'alpha','sub':1}"); | |
| 441 ExpectSuccess(spec, "({e: 'beta', sub: 1})", "{'e':'beta','sub':1}"); | |
| 442 ExpectFailure(spec, "({e: 'gamma', sub: 1})", | |
| 443 PropertyError("e", InvalidEnumValue(valid_enums))); | |
| 444 ExpectFailure(spec, "({e: 'alpha'})", MissingRequiredProperty("sub")); | |
| 445 } | |
| 446 | |
| 447 { | |
| 448 const char kObjectWithRefObjectSpec[] = | |
| 449 "{" | |
| 450 " 'name': 'objWithRefObject'," | |
| 451 " 'type': 'object'," | |
| 452 " 'properties': {" | |
| 453 " 'o': {'$ref': 'refObj'}" | |
| 454 " }" | |
| 455 "}"; | |
| 456 ArgumentSpec spec(*ValueFromString(kObjectWithRefObjectSpec)); | |
| 457 ExpectSuccess(spec, "({o: {prop1: 'foo'}})", "{'o':{'prop1':'foo'}}"); | |
| 458 ExpectSuccess(spec, "({o: {prop1: 'foo', prop2: 2}})", | |
| 459 "{'o':{'prop1':'foo','prop2':2}}"); | |
| 460 ExpectFailure( | |
| 461 spec, "({o: {prop1: 1}})", | |
| 462 PropertyError("o", PropertyError("prop1", InvalidType(kTypeString, | |
| 463 kTypeInteger)))); | |
| 464 } | |
| 465 | |
| 466 { | |
| 467 const char kRefEnumListSpec[] = | |
| 468 "{'type': 'array', 'items': {'$ref': 'refEnum'}}"; | |
| 469 ArgumentSpec spec(*ValueFromString(kRefEnumListSpec)); | |
| 470 ExpectSuccess(spec, "['alpha']", "['alpha']"); | |
| 471 ExpectSuccess(spec, "['alpha', 'alpha']", "['alpha','alpha']"); | |
| 472 ExpectSuccess(spec, "['alpha', 'beta']", "['alpha','beta']"); | |
| 473 ExpectFailure(spec, "['alpha', 'beta', 'gamma']", | |
| 474 IndexError(2u, InvalidEnumValue(valid_enums))); | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 TEST_F(ArgumentSpecUnitTest, TypeChoicesTest) { | |
| 479 using namespace api_errors; | |
| 480 { | |
| 481 const char kSimpleChoices[] = | |
| 482 "{'choices': [{'type': 'string'}, {'type': 'integer'}]}"; | |
| 483 ArgumentSpec spec(*ValueFromString(kSimpleChoices)); | |
| 484 ExpectSuccess(spec, "'alpha'", "'alpha'"); | |
| 485 ExpectSuccess(spec, "42", "42"); | |
| 486 ExpectFailure(spec, "true", InvalidChoice()); | |
| 487 } | |
| 488 | |
| 489 { | |
| 490 const char kComplexChoices[] = | |
| 491 "{" | |
| 492 " 'choices': [" | |
| 493 " {'type': 'array', 'items': {'type': 'string'}}," | |
| 494 " {'type': 'object', 'properties': {'prop1': {'type': 'string'}}}" | |
| 495 " ]" | |
| 496 "}"; | |
| 497 ArgumentSpec spec(*ValueFromString(kComplexChoices)); | |
| 498 ExpectSuccess(spec, "['alpha']", "['alpha']"); | |
| 499 ExpectSuccess(spec, "['alpha', 'beta']", "['alpha','beta']"); | |
| 500 ExpectSuccess(spec, "({prop1: 'alpha'})", "{'prop1':'alpha'}"); | |
| 501 ExpectFailure(spec, "({prop1: 1})", InvalidChoice()); | |
| 502 ExpectFailure(spec, "'alpha'", InvalidChoice()); | |
| 503 ExpectFailure(spec, "42", InvalidChoice()); | |
| 504 } | |
| 505 } | |
| 506 | |
| 507 TEST_F(ArgumentSpecUnitTest, AdditionalPropertiesTest) { | |
| 508 using namespace api_errors; | |
| 509 { | |
| 510 const char kOnlyAnyAdditionalProperties[] = | |
| 511 "{" | |
| 512 " 'type': 'object'," | |
| 513 " 'additionalProperties': {'type': 'any'}" | |
| 514 "}"; | |
| 515 ArgumentSpec spec(*ValueFromString(kOnlyAnyAdditionalProperties)); | |
| 516 ExpectSuccess(spec, "({prop1: 'alpha', prop2: 42, prop3: {foo: 'bar'}})", | |
| 517 "{'prop1':'alpha','prop2':42,'prop3':{'foo':'bar'}}"); | |
| 518 ExpectSuccess(spec, "({})", "{}"); | |
| 519 // Test some crazy keys. | |
| 520 ExpectSuccess(spec, | |
| 521 "var x = {};\n" | |
| 522 "var y = {prop1: 'alpha'};\n" | |
| 523 "y[42] = 'beta';\n" | |
| 524 "y[x] = 'gamma';\n" | |
| 525 "y[undefined] = 'delta';\n" | |
| 526 "y;", | |
| 527 "{'42':'beta','[object Object]':'gamma','prop1':'alpha'," | |
| 528 "'undefined':'delta'}"); | |
| 529 // We (typically*, see "Fun case" below) don't serialize properties on an | |
| 530 // object prototype. | |
| 531 ExpectSuccess(spec, | |
| 532 "({\n" | |
| 533 " __proto__: {protoProp: 'proto'},\n" | |
| 534 " instanceProp: 'instance'\n" | |
| 535 "})", | |
| 536 "{'instanceProp':'instance'}"); | |
| 537 // Fun case: Remove a property as a result of getting another. Currently, | |
| 538 // we don't check each property with HasOwnProperty() during iteration, so | |
| 539 // Fun case: Remove a property as a result of getting another. Currently, | |
| 540 // we don't check each property with HasOwnProperty() during iteration, so | |
| 541 // we still try to serialize it. But we don't serialize undefined, so in the | |
| 542 // case of the property not being defined on the prototype, this works as | |
| 543 // expected. | |
| 544 ExpectSuccess(spec, | |
| 545 "var x = {};\n" | |
| 546 "Object.defineProperty(\n" | |
| 547 " x, 'alpha',\n" | |
| 548 " {\n" | |
| 549 " enumerable: true,\n" | |
| 550 " get: () => { delete x.omega; return 'alpha'; }\n" | |
| 551 " });\n" | |
| 552 "x.omega = 'omega';\n" | |
| 553 "x;", | |
| 554 "{'alpha':'alpha'}"); | |
| 555 // Fun case continued: If an object removes the property, and the property | |
| 556 // *is* present on the prototype, then we serialize the value from the | |
| 557 // prototype. This is inconsistent, but only manifests scripts are doing | |
| 558 // crazy things (and is still safe). | |
| 559 // TODO(devlin): We *could* add a HasOwnProperty() check, in which case | |
| 560 // the result of this call should be {'alpha':'alpha'}. | |
| 561 ExpectSuccess(spec, | |
| 562 "var x = {\n" | |
| 563 " __proto__: { omega: 'different omega' }\n" | |
| 564 "};\n" | |
| 565 "Object.defineProperty(\n" | |
| 566 " x, 'alpha',\n" | |
| 567 " {\n" | |
| 568 " enumerable: true,\n" | |
| 569 " get: () => { delete x.omega; return 'alpha'; }\n" | |
| 570 " });\n" | |
| 571 "x.omega = 'omega';\n" | |
| 572 "x;", | |
| 573 "{'alpha':'alpha','omega':'different omega'}"); | |
| 574 } | |
| 575 { | |
| 576 const char kPropertiesAndAnyAdditionalProperties[] = | |
| 577 "{" | |
| 578 " 'type': 'object'," | |
| 579 " 'properties': {" | |
| 580 " 'prop1': {'type': 'string'}" | |
| 581 " }," | |
| 582 " 'additionalProperties': {'type': 'any'}" | |
| 583 "}"; | |
| 584 ArgumentSpec spec(*ValueFromString(kPropertiesAndAnyAdditionalProperties)); | |
| 585 ExpectSuccess(spec, "({prop1: 'alpha', prop2: 42, prop3: {foo: 'bar'}})", | |
| 586 "{'prop1':'alpha','prop2':42,'prop3':{'foo':'bar'}}"); | |
| 587 // Additional properties are optional. | |
| 588 ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}"); | |
| 589 ExpectFailure(spec, "({prop2: 42, prop3: {foo: 'bar'}})", | |
| 590 MissingRequiredProperty("prop1")); | |
| 591 ExpectFailure( | |
| 592 spec, "({prop1: 42})", | |
| 593 PropertyError("prop1", InvalidType(kTypeString, kTypeInteger))); | |
| 594 } | |
| 595 { | |
| 596 const char kTypedAdditionalProperties[] = | |
| 597 "{" | |
| 598 " 'type': 'object'," | |
| 599 " 'additionalProperties': {'type': 'string'}" | |
| 600 "}"; | |
| 601 ArgumentSpec spec(*ValueFromString(kTypedAdditionalProperties)); | |
| 602 ExpectSuccess(spec, "({prop1: 'alpha', prop2: 'beta', prop3: 'gamma'})", | |
| 603 "{'prop1':'alpha','prop2':'beta','prop3':'gamma'}"); | |
| 604 ExpectFailure( | |
| 605 spec, "({prop1: 'alpha', prop2: 42})", | |
| 606 PropertyError("prop2", InvalidType(kTypeString, kTypeInteger))); | |
| 607 } | |
| 608 } | |
| 609 | |
| 610 TEST_F(ArgumentSpecUnitTest, InstanceOfTest) { | |
| 611 using namespace api_errors; | |
| 612 { | |
| 613 const char kInstanceOfRegExp[] = | |
| 614 "{" | |
| 615 " 'type': 'object'," | |
| 616 " 'isInstanceOf': 'RegExp'" | |
| 617 "}"; | |
| 618 ArgumentSpec spec(*ValueFromString(kInstanceOfRegExp)); | |
| 619 ExpectSuccess(spec, "(new RegExp())", "{}"); | |
| 620 ExpectSuccess(spec, "({ __proto__: RegExp.prototype })", "{}"); | |
| 621 ExpectSuccess(spec, | |
| 622 "(function() {\n" | |
| 623 " function subRegExp() {}\n" | |
| 624 " subRegExp.prototype = { __proto__: RegExp.prototype };\n" | |
| 625 " return new subRegExp();\n" | |
| 626 "})()", | |
| 627 "{}"); | |
| 628 ExpectSuccess(spec, | |
| 629 "(function() {\n" | |
| 630 " function RegExp() {}\n" | |
| 631 " return new RegExp();\n" | |
| 632 "})()", | |
| 633 "{}"); | |
| 634 ExpectFailure(spec, "({})", NotAnInstance("RegExp")); | |
| 635 ExpectFailure(spec, "('')", InvalidType("RegExp", kTypeString)); | |
| 636 ExpectFailure(spec, "('.*')", InvalidType("RegExp", kTypeString)); | |
| 637 ExpectFailure(spec, "({ __proto__: Date.prototype })", | |
| 638 NotAnInstance("RegExp")); | |
| 639 } | |
| 640 | |
| 641 { | |
| 642 const char kInstanceOfCustomClass[] = | |
| 643 "{" | |
| 644 " 'type': 'object'," | |
| 645 " 'isInstanceOf': 'customClass'" | |
| 646 "}"; | |
| 647 ArgumentSpec spec(*ValueFromString(kInstanceOfCustomClass)); | |
| 648 ExpectSuccess(spec, | |
| 649 "(function() {\n" | |
| 650 " function customClass() {}\n" | |
| 651 " return new customClass();\n" | |
| 652 "})()", | |
| 653 "{}"); | |
| 654 ExpectSuccess(spec, | |
| 655 "(function() {\n" | |
| 656 " function customClass() {}\n" | |
| 657 " function otherClass() {}\n" | |
| 658 " otherClass.prototype = \n" | |
| 659 " { __proto__: customClass.prototype };\n" | |
| 660 " return new otherClass();\n" | |
| 661 "})()", | |
| 662 "{}"); | |
| 663 ExpectFailure(spec, "({})", NotAnInstance("customClass")); | |
| 664 ExpectFailure(spec, | |
| 665 "(function() {\n" | |
| 666 " function otherClass() {}\n" | |
| 667 " return new otherClass();\n" | |
| 668 "})()", | |
| 669 NotAnInstance("customClass")); | |
| 670 } | |
| 671 } | |
| 672 | |
| 673 TEST_F(ArgumentSpecUnitTest, MinAndMaxLengths) { | |
| 674 using namespace api_errors; | |
| 675 { | |
| 676 const char kMinLengthString[] = "{'type': 'string', 'minLength': 3}"; | |
| 677 ArgumentSpec spec(*ValueFromString(kMinLengthString)); | |
| 678 ExpectSuccess(spec, "'aaa'", "'aaa'"); | |
| 679 ExpectSuccess(spec, "'aaaa'", "'aaaa'"); | |
| 680 ExpectFailure(spec, "'aa'", TooFewStringChars(3, 2)); | |
| 681 ExpectFailure(spec, "''", TooFewStringChars(3, 0)); | |
| 682 } | |
| 683 | |
| 684 { | |
| 685 const char kMaxLengthString[] = "{'type': 'string', 'maxLength': 3}"; | |
| 686 ArgumentSpec spec(*ValueFromString(kMaxLengthString)); | |
| 687 ExpectSuccess(spec, "'aaa'", "'aaa'"); | |
| 688 ExpectSuccess(spec, "'aa'", "'aa'"); | |
| 689 ExpectSuccess(spec, "''", "''"); | |
| 690 ExpectFailure(spec, "'aaaa'", TooManyStringChars(3, 4)); | |
| 691 } | |
| 692 | |
| 693 { | |
| 694 const char kMinLengthArray[] = | |
| 695 "{'type': 'array', 'items': {'type': 'integer'}, 'minItems': 3}"; | |
| 696 ArgumentSpec spec(*ValueFromString(kMinLengthArray)); | |
| 697 ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]"); | |
| 698 ExpectSuccess(spec, "[1, 2, 3, 4]", "[1,2,3,4]"); | |
| 699 ExpectFailure(spec, "[1, 2]", TooFewArrayItems(3, 2)); | |
| 700 ExpectFailure(spec, "[]", TooFewArrayItems(3, 0)); | |
| 701 } | |
| 702 | |
| 703 { | |
| 704 const char kMaxLengthArray[] = | |
| 705 "{'type': 'array', 'items': {'type': 'integer'}, 'maxItems': 3}"; | |
| 706 ArgumentSpec spec(*ValueFromString(kMaxLengthArray)); | |
| 707 ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]"); | |
| 708 ExpectSuccess(spec, "[1, 2]", "[1,2]"); | |
| 709 ExpectSuccess(spec, "[]", "[]"); | |
| 710 ExpectFailure(spec, "[1, 2, 3, 4]", TooManyArrayItems(3, 4)); | |
| 711 } | |
| 712 } | |
| 713 | |
| 714 TEST_F(ArgumentSpecUnitTest, PreserveNull) { | |
| 715 using namespace api_errors; | |
| 716 { | |
| 717 const char kObjectSpec[] = | |
| 718 "{" | |
| 719 " 'type': 'object'," | |
| 720 " 'additionalProperties': {'type': 'any'}," | |
| 721 " 'preserveNull': true" | |
| 722 "}"; | |
| 723 ArgumentSpec spec(*ValueFromString(kObjectSpec)); | |
| 724 ExpectSuccess(spec, "({foo: 1, bar: null})", "{'bar':null,'foo':1}"); | |
| 725 // Subproperties shouldn't preserve null (if not specified). | |
| 726 ExpectSuccess(spec, "({prop: {subprop1: 'foo', subprop2: null}})", | |
| 727 "{'prop':{'subprop1':'foo'}}"); | |
| 728 } | |
| 729 | |
| 730 { | |
| 731 const char kObjectSpec[] = | |
| 732 "{" | |
| 733 " 'type': 'object'," | |
| 734 " 'additionalProperties': {'type': 'any', 'preserveNull': true}," | |
| 735 " 'preserveNull': true" | |
| 736 "}"; | |
| 737 ArgumentSpec spec(*ValueFromString(kObjectSpec)); | |
| 738 ExpectSuccess(spec, "({foo: 1, bar: null})", "{'bar':null,'foo':1}"); | |
| 739 // Here, subproperties should preserve null. | |
| 740 ExpectSuccess(spec, "({prop: {subprop1: 'foo', subprop2: null}})", | |
| 741 "{'prop':{'subprop1':'foo','subprop2':null}}"); | |
| 742 } | |
| 743 | |
| 744 { | |
| 745 const char kObjectSpec[] = | |
| 746 "{" | |
| 747 " 'type': 'object'," | |
| 748 " 'properties': {'prop1': {'type': 'string', 'optional': true}}," | |
| 749 " 'preserveNull': true" | |
| 750 "}"; | |
| 751 ArgumentSpec spec(*ValueFromString(kObjectSpec)); | |
| 752 ExpectSuccess(spec, "({})", "{}"); | |
| 753 ExpectSuccess(spec, "({prop1: null})", "{'prop1':null}"); | |
| 754 ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}"); | |
| 755 // Undefined should not be preserved. | |
| 756 ExpectSuccess(spec, "({prop1: undefined})", "{}"); | |
| 757 // preserveNull shouldn't affect normal parsing restrictions. | |
| 758 ExpectFailure( | |
| 759 spec, "({prop1: 1})", | |
| 760 PropertyError("prop1", InvalidType(kTypeString, kTypeInteger))); | |
| 761 } | |
| 762 } | |
| 763 | |
| 764 TEST_F(ArgumentSpecUnitTest, NaNFun) { | |
| 765 using namespace api_errors; | |
| 766 | |
| 767 { | |
| 768 const char kAnySpec[] = "{'type': 'any'}"; | |
| 769 ArgumentSpec spec(*ValueFromString(kAnySpec)); | |
| 770 ExpectFailure(spec, "NaN", UnserializableValue()); | |
| 771 } | |
| 772 | |
| 773 { | |
| 774 const char kObjectWithAnyPropertiesSpec[] = | |
| 775 "{'type': 'object', 'additionalProperties': {'type': 'any'}}"; | |
| 776 ArgumentSpec spec(*ValueFromString(kObjectWithAnyPropertiesSpec)); | |
| 777 ExpectSuccess(spec, "({foo: NaN, bar: 'baz'})", "{'bar':'baz'}"); | |
| 778 } | |
| 779 } | |
| 780 | |
| 781 TEST_F(ArgumentSpecUnitTest, GetTypeName) { | |
| 782 struct { | |
| 783 ArgumentType type; | |
| 784 const char* expected_type_name; | |
| 785 } simple_cases[] = { | |
| 786 {ArgumentType::BOOLEAN, api_errors::kTypeBoolean}, | |
| 787 {ArgumentType::INTEGER, api_errors::kTypeInteger}, | |
| 788 {ArgumentType::OBJECT, api_errors::kTypeObject}, | |
| 789 {ArgumentType::LIST, api_errors::kTypeList}, | |
| 790 {ArgumentType::BINARY, api_errors::kTypeBinary}, | |
| 791 {ArgumentType::FUNCTION, api_errors::kTypeFunction}, | |
| 792 {ArgumentType::ANY, api_errors::kTypeAny}, | |
| 793 }; | |
| 794 | |
| 795 for (const auto& test_case : simple_cases) { | |
| 796 ArgumentSpec spec(test_case.type); | |
| 797 EXPECT_EQ(test_case.expected_type_name, spec.GetTypeName()); | |
| 798 } | |
| 799 | |
| 800 { | |
| 801 const char kRefName[] = "someRef"; | |
| 802 ArgumentSpec ref_spec(ArgumentType::REF); | |
| 803 ref_spec.set_ref(kRefName); | |
| 804 EXPECT_EQ(kRefName, ref_spec.GetTypeName()); | |
| 805 } | |
| 806 | |
| 807 { | |
| 808 std::vector<std::unique_ptr<ArgumentSpec>> choices; | |
| 809 choices.push_back(base::MakeUnique<ArgumentSpec>(ArgumentType::INTEGER)); | |
| 810 choices.push_back(base::MakeUnique<ArgumentSpec>(ArgumentType::STRING)); | |
| 811 ArgumentSpec choices_spec(ArgumentType::CHOICES); | |
| 812 choices_spec.set_choices(std::move(choices)); | |
| 813 EXPECT_EQ("[integer|string]", choices_spec.GetTypeName()); | |
| 814 } | |
| 815 } | |
| 816 | |
| 817 } // namespace extensions | |
| OLD | NEW |