/json_schema_validator/tests/test_schema.py

https://github.com/okoye/json-schema-validator · Python · 773 lines · 739 code · 8 blank · 26 comment · 8 complexity · 41fa35bfbf2d36798897428cd09dc4f3 MD5 · raw file

  1. # Copyright (C) 2010, 2011 Linaro Limited
  2. #
  3. # Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
  4. #
  5. # This file is part of json-schema-validator.
  6. #
  7. # json-schema-validator is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Lesser General Public License version 3
  9. # as published by the Free Software Foundation
  10. #
  11. # json-schema-validator is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Lesser General Public License
  17. # along with json-schema-validator. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. Unit tests for JSON schema
  20. """
  21. import simplejson
  22. from testscenarios import TestWithScenarios
  23. from testtools import TestCase
  24. from json_schema_validator.errors import SchemaError
  25. from json_schema_validator.schema import Schema
  26. class SchemaTests(TestWithScenarios, TestCase):
  27. scenarios = [
  28. ('type_default', {
  29. 'schema': '{}',
  30. 'expected': {
  31. 'type': ['any']
  32. },
  33. }),
  34. ('type_string', {
  35. 'schema': '{"type": "string"}',
  36. 'expected': {
  37. 'type': ['string']
  38. },
  39. }),
  40. ('type_number', {
  41. 'schema': '{"type": "number"}',
  42. 'expected': {
  43. 'type': ['number']
  44. },
  45. }),
  46. ('type_integer', {
  47. 'schema': '{"type": "integer"}',
  48. 'expected': {
  49. 'type': ['integer']
  50. },
  51. }),
  52. ('type_boolean', {
  53. 'schema': '{"type": "boolean"}',
  54. 'expected': {
  55. 'type': ['boolean']
  56. },
  57. }),
  58. ('type_object', {
  59. 'schema': '{"type": "object"}',
  60. 'expected': {
  61. 'type': ['object']
  62. },
  63. }),
  64. ('type_array', {
  65. 'schema': '{"type": "array"}',
  66. 'expected': {
  67. 'type': ['array']
  68. },
  69. }),
  70. ('type_complex_subtype', {
  71. 'schema': '{"type": {}}',
  72. 'expected': {
  73. 'type': [{}],
  74. },
  75. }),
  76. ('type_list', {
  77. 'schema': '{"type": ["string", "number"]}',
  78. 'expected': {
  79. 'type': ["string", "number"],
  80. },
  81. }),
  82. ('type_wrong_type', {
  83. 'schema': '{"type": 5}',
  84. 'access': 'type',
  85. 'raises': SchemaError(
  86. "type value 5 is not a simple type name,"
  87. " nested schema nor a list of those"),
  88. }),
  89. ('type_not_a_simple_type_name', {
  90. 'schema': '{"type": "foobar"}',
  91. 'access': 'type',
  92. 'raises': SchemaError(
  93. "type value 'foobar' is not a simple type name"),
  94. }),
  95. ('type_list_duplicates', {
  96. 'schema': '{"type": ["string", "string"]}',
  97. 'access': 'type',
  98. 'raises': SchemaError(
  99. "type value ['string', 'string'] contains duplicate"
  100. " element 'string'")
  101. }),
  102. ('properties_default', {
  103. 'schema': '{}',
  104. 'expected': {
  105. 'properties': {},
  106. },
  107. }),
  108. ('properties_example', {
  109. 'schema': '{"properties": {"prop": {"type": "number"}}}',
  110. 'expected': {
  111. 'properties': {"prop": {"type": "number"}},
  112. },
  113. }),
  114. ('properties_wrong_type', {
  115. 'schema': '{"properties": 5}',
  116. 'access': 'properties',
  117. 'raises': SchemaError(
  118. 'properties value 5 is not an object'),
  119. }),
  120. ('items_default', {
  121. 'schema': '{}',
  122. 'expected': {
  123. 'items': {},
  124. },
  125. }),
  126. ('items_tuple', {
  127. 'schema': '{"items": [{}, {}]}',
  128. 'expected': {
  129. 'items': [{}, {}],
  130. },
  131. }),
  132. ('items_each', {
  133. 'schema': '{"items": {"type": "number"}}',
  134. 'expected': {
  135. 'items': {"type": "number"},
  136. },
  137. }),
  138. ('items_wrong_type', {
  139. 'schema': '{"items": 5}',
  140. 'access': 'items',
  141. 'raises': SchemaError(
  142. 'items value 5 is neither a list nor an object'),
  143. }),
  144. ('optional_default', {
  145. 'schema': '{}',
  146. 'expected': {
  147. 'optional': False,
  148. },
  149. }),
  150. ('optional_true', {
  151. 'schema': '{"optional": true}',
  152. 'expected': {
  153. 'optional': True,
  154. },
  155. }),
  156. ('optional_false', {
  157. 'schema': '{"optional": false}',
  158. 'expected': {
  159. 'optional': False,
  160. },
  161. }),
  162. ('optional_wrong_type', {
  163. 'schema': '{"optional": 5}',
  164. 'access': 'optional',
  165. 'raises': SchemaError(
  166. 'optional value 5 is not a boolean'),
  167. }),
  168. ('additionalProperties_default', {
  169. 'schema': '{}',
  170. 'expected': {
  171. 'additionalProperties': {}
  172. },
  173. }),
  174. ('additionalProperties_false', {
  175. 'schema': '{"additionalProperties": false}',
  176. 'expected': {
  177. "additionalProperties": False,
  178. },
  179. }),
  180. ('additionalProperties_object', {
  181. 'schema': '{"additionalProperties": {"type": "number"}}',
  182. 'expected': {
  183. "additionalProperties": {"type": "number"},
  184. },
  185. }),
  186. ('additionalProperties_wrong_type', {
  187. 'schema': '{"additionalProperties": 5}',
  188. 'access': 'additionalProperties',
  189. 'raises': SchemaError(
  190. 'additionalProperties value 5 is neither false nor an'
  191. ' object'),
  192. }),
  193. ('requires_default', {
  194. 'schema': '{}',
  195. 'expected': {
  196. 'requires': {},
  197. },
  198. }),
  199. ('requires_property_name', {
  200. 'schema': '{"requires": "other"}',
  201. 'expected': {
  202. 'requires': "other",
  203. },
  204. }),
  205. ('requires_schema', {
  206. 'schema': '{"requires": {"properties": {"other": {"type": "number"}}}}',
  207. 'expected': {
  208. 'requires': {
  209. 'properties': {
  210. 'other': {
  211. 'type': 'number'
  212. },
  213. },
  214. },
  215. },
  216. }),
  217. ('requires_wrong_value', {
  218. 'schema': '{"requires": 5}',
  219. 'access': 'requires',
  220. 'raises': SchemaError(
  221. 'requires value 5 is neither a string nor an object'),
  222. }),
  223. ('minimum_default', {
  224. 'schema': '{}',
  225. 'expected': {
  226. 'minimum': None
  227. },
  228. }),
  229. ('minimum_integer', {
  230. 'schema': '{"minimum": 5}',
  231. 'expected': {
  232. 'minimum': 5
  233. },
  234. }),
  235. ('minimum_float', {
  236. 'schema': '{"minimum": 3.5}',
  237. 'expected': {
  238. 'minimum': 3.5
  239. },
  240. }),
  241. ('minimum_wrong_type', {
  242. 'schema': '{"minimum": "foobar"}',
  243. 'access': 'minimum',
  244. 'raises': SchemaError(
  245. 'minimum value \'foobar\' is not a numeric type')
  246. }),
  247. ('maximum_default', {
  248. 'schema': '{}',
  249. 'expected': {
  250. 'maximum': None
  251. },
  252. }),
  253. ('maximum_integer', {
  254. 'schema': '{"maximum": 5}',
  255. 'expected': {
  256. 'maximum': 5
  257. },
  258. }),
  259. ('maximum_float', {
  260. 'schema': '{"maximum": 3.5}',
  261. 'expected': {
  262. 'maximum': 3.5
  263. },
  264. }),
  265. ('maximum_wrong_type', {
  266. 'schema': '{"maximum": "foobar"}',
  267. 'access': 'maximum',
  268. 'raises': SchemaError(
  269. 'maximum value \'foobar\' is not a numeric type')
  270. }),
  271. ('minimumCanEqual_default', {
  272. 'schema': '{"minimum": 5}',
  273. 'expected': {
  274. 'minimum': 5,
  275. 'minimumCanEqual': True
  276. },
  277. }),
  278. ('minimumCanEqual_false', {
  279. 'schema': '{"minimum": 5, "minimumCanEqual": false}',
  280. 'expected': {
  281. 'minimum': 5,
  282. 'minimumCanEqual': False,
  283. },
  284. }),
  285. ('minimumCanEqual_true', {
  286. 'schema': '{"minimum": 5, "minimumCanEqual": true}',
  287. 'expected': {
  288. 'minimum': 5,
  289. 'minimumCanEqual': True
  290. },
  291. }),
  292. ('minimumCanEqual_without_minimum', {
  293. 'schema': '{}',
  294. 'access': 'minimumCanEqual',
  295. 'raises': SchemaError(
  296. "minimumCanEqual requires presence of minimum"),
  297. }),
  298. ('minimumCanEqual_wrong_type', {
  299. 'schema': '{"minimum": 5, "minimumCanEqual": 5}',
  300. 'access': 'minimumCanEqual',
  301. 'raises': SchemaError(
  302. "minimumCanEqual value 5 is not a boolean"),
  303. }),
  304. ('maximumCanEqual_default', {
  305. 'schema': '{"maximum": 5}',
  306. 'expected': {
  307. 'maximum': 5,
  308. 'maximumCanEqual': True
  309. },
  310. }),
  311. ('maximumCanEqual_false', {
  312. 'schema': '{"maximum": 5, "maximumCanEqual": false}',
  313. 'expected': {
  314. 'maximum': 5,
  315. 'maximumCanEqual': False,
  316. },
  317. }),
  318. ('maximumCanEqual_true', {
  319. 'schema': '{"maximum": 5, "maximumCanEqual": true}',
  320. 'expected': {
  321. 'maximum': 5,
  322. 'maximumCanEqual': True
  323. },
  324. }),
  325. ('maximumCanEqual_without_maximum', {
  326. 'schema': '{}',
  327. 'access': 'maximumCanEqual',
  328. 'raises': SchemaError(
  329. "maximumCanEqual requires presence of maximum"),
  330. }),
  331. ('maximumCanEqual_wrong_type', {
  332. 'schema': '{"maximum": 5, "maximumCanEqual": 5}',
  333. 'access': 'maximumCanEqual',
  334. 'raises': SchemaError(
  335. "maximumCanEqual value 5 is not a boolean"),
  336. }),
  337. ("minItems_default", {
  338. 'schema': '{}',
  339. 'expected': {
  340. 'minItems': 0,
  341. },
  342. }),
  343. ("minItems_integer", {
  344. 'schema': '{"minItems": 13}',
  345. 'expected': {
  346. 'minItems': 13,
  347. },
  348. }),
  349. ("minItems_zero", {
  350. 'schema': '{"minItems": 0}',
  351. 'expected': {
  352. 'minItems': 0,
  353. },
  354. }),
  355. ("minItems_minus_one", {
  356. 'schema': '{"minItems": -1}',
  357. 'access': 'minItems',
  358. 'raises': SchemaError(
  359. "minItems value -1 cannot be negative"),
  360. }),
  361. ("minItems_wrong_type", {
  362. 'schema': '{"minItems": "foobar"}',
  363. 'access': 'minItems',
  364. 'raises': SchemaError(
  365. "minItems value 'foobar' is not an integer"),
  366. }),
  367. ("maxItems_default", {
  368. 'schema': '{}',
  369. 'expected': {
  370. 'maxItems': None,
  371. },
  372. }),
  373. ("maxItems_integer", {
  374. 'schema': '{"maxItems": 13}',
  375. 'expected': {
  376. 'maxItems': 13,
  377. },
  378. }),
  379. ("maxItems_zero", {
  380. 'schema': '{"maxItems": 0}',
  381. 'expected': {
  382. 'maxItems': 0,
  383. },
  384. }),
  385. ("maxItems_minus_one", {
  386. 'schema': '{"maxItems": -1}',
  387. 'expected': {
  388. 'maxItems': -1
  389. },
  390. }),
  391. ("maxItems_wrong_type", {
  392. 'schema': '{"maxItems": "foobar"}',
  393. 'access': 'maxItems',
  394. 'raises': SchemaError(
  395. "maxItems value 'foobar' is not an integer"),
  396. }),
  397. ("uniqueItems_default", {
  398. 'schema': '{}',
  399. 'expected': {
  400. 'uniqueItems': False
  401. }
  402. }),
  403. ("uniqueItems_true", {
  404. 'schema': '{"uniqueItems": true}',
  405. 'expected': {
  406. 'uniqueItems': True
  407. }
  408. }),
  409. ("uniqueItems_false", {
  410. 'schema': '{"uniqueItems": false}',
  411. 'expected': {
  412. 'uniqueItems': False
  413. }
  414. }),
  415. ("uniqueItems_wrong_type", {
  416. 'schema': '{"uniqueItems": 5}',
  417. 'access': 'uniqueItems',
  418. 'raises': SchemaError(
  419. "uniqueItems value 5 is not a boolean")
  420. }),
  421. ("pattern_default", {
  422. 'schema': '{}',
  423. 'expected': {
  424. 'pattern': None,
  425. },
  426. }),
  427. #("pattern_simple", {
  428. # 'schema': '{"pattern": "foo|bar"}',
  429. # 'expected': {
  430. # 'pattern': re.compile('foo|bar'),
  431. # },
  432. #}),
  433. ("pattern_broken", {
  434. 'schema': '{"pattern": "[unterminated"}',
  435. 'access': 'pattern',
  436. 'raises': SchemaError(
  437. "pattern value '[unterminated' is not a valid regular"
  438. " expression: unexpected end of regular expression"),
  439. }),
  440. ("minLength_default", {
  441. 'schema': '{}',
  442. 'expected': {
  443. 'minLength': 0,
  444. },
  445. }),
  446. ("minLength_integer", {
  447. 'schema': '{"minLength": 13}',
  448. 'expected': {
  449. 'minLength': 13,
  450. },
  451. }),
  452. ("minLength_zero", {
  453. 'schema': '{"minLength": 0}',
  454. 'expected': {
  455. 'minLength': 0,
  456. },
  457. }),
  458. ("minLength_minus_one", {
  459. 'schema': '{"minLength": -1}',
  460. 'access': 'minLength',
  461. 'raises': SchemaError(
  462. "minLength value -1 cannot be negative"),
  463. }),
  464. ("minLength_wrong_type", {
  465. 'schema': '{"minLength": "foobar"}',
  466. 'access': 'minLength',
  467. 'raises': SchemaError(
  468. "minLength value 'foobar' is not an integer"),
  469. }),
  470. ("maxLength_default", {
  471. 'schema': '{}',
  472. 'expected': {
  473. 'maxLength': None,
  474. },
  475. }),
  476. ("maxLength_integer", {
  477. 'schema': '{"maxLength": 13}',
  478. 'expected': {
  479. 'maxLength': 13,
  480. },
  481. }),
  482. ("maxLength_zero", {
  483. 'schema': '{"maxLength": 0}',
  484. 'expected': {
  485. 'maxLength': 0,
  486. },
  487. }),
  488. ("maxLength_minus_one", {
  489. 'schema': '{"maxLength": -1}',
  490. 'expected': {
  491. 'maxLength': -1
  492. },
  493. }),
  494. ("maxLength_wrong_type", {
  495. 'schema': '{"maxLength": "foobar"}',
  496. 'access': 'maxLength',
  497. 'raises': SchemaError(
  498. "maxLength value 'foobar' is not an integer"),
  499. }),
  500. ("enum_default", {
  501. 'schema': '{}',
  502. 'expected': {
  503. 'enum': None,
  504. }
  505. }),
  506. ("enum_simple", {
  507. 'schema': '{"enum": ["foo", "bar"]}',
  508. 'expected': {
  509. 'enum': ["foo", "bar"],
  510. }
  511. }),
  512. ("enum_mixed", {
  513. 'schema': '{"enum": [5, false, "foobar"]}',
  514. 'expected': {
  515. 'enum':[5, False, "foobar"]
  516. }
  517. }),
  518. ("enum_wrong_type", {
  519. 'schema': '{"enum": "foobar"}',
  520. 'access': 'enum',
  521. 'raises': SchemaError(
  522. "enum value 'foobar' is not a list"),
  523. }),
  524. ("enum_too_short", {
  525. 'schema': '{"enum": []}',
  526. 'access': 'enum',
  527. 'raises': SchemaError(
  528. "enum value [] does not contain any elements"),
  529. }),
  530. ("enum_duplicates", {
  531. 'schema': '{"enum": ["foo", "foo"]}',
  532. 'access': 'enum',
  533. 'raises': SchemaError(
  534. "enum value ['foo', 'foo'] contains duplicate element"
  535. " 'foo'"),
  536. }),
  537. ("title_default", {
  538. 'schema': '{}',
  539. 'expected': {
  540. 'title': None,
  541. },
  542. }),
  543. ("title_simple", {
  544. 'schema': '{"title": "foobar"}',
  545. 'expected': {
  546. 'title': "foobar",
  547. },
  548. }),
  549. ("title_wrong_type", {
  550. 'schema': '{"title": 5}',
  551. 'access': 'title',
  552. 'raises': SchemaError('title value 5 is not a string')
  553. }),
  554. ("description_default", {
  555. 'schema': '{}',
  556. 'expected': {
  557. 'description': None,
  558. },
  559. }),
  560. ("description_simple", {
  561. 'schema': '{"description": "foobar"}',
  562. 'expected': {
  563. 'description': "foobar",
  564. },
  565. }),
  566. ("description_wrong_type", {
  567. 'schema': '{"description": 5}',
  568. 'access': 'description',
  569. 'raises': SchemaError('description value 5 is not a string')
  570. }),
  571. ("format_default", {
  572. 'schema': '{}',
  573. 'expected': {
  574. 'format': None
  575. },
  576. }),
  577. ("format_date_time", {
  578. 'schema': '{"format": "date-time"}',
  579. 'expected': {
  580. 'format': "date-time"
  581. },
  582. }),
  583. ("format_wrong_type", {
  584. 'schema': '{"format": 5}',
  585. 'access': 'format',
  586. 'raises': SchemaError('format value 5 is not a string')
  587. }),
  588. ("format_not_implemented", {
  589. 'schema': '{"format": "color"}',
  590. 'access': 'format',
  591. 'raises': NotImplementedError(
  592. "format value 'color' is not supported")
  593. }),
  594. ("contentEncoding_default", {
  595. 'schema': '{}',
  596. 'expected': {
  597. 'contentEncoding': None,
  598. }
  599. }),
  600. ("contentEncoding_base64", {
  601. 'schema': '{"contentEncoding": "base64"}',
  602. 'expected': {
  603. 'contentEncoding': "base64",
  604. },
  605. }),
  606. ("contentEncoding_base64_mixed_case", {
  607. 'schema': '{"contentEncoding": "BAsE64"}',
  608. 'expected': {
  609. 'contentEncoding': 'BAsE64',
  610. },
  611. }),
  612. ("contentEncoding_unsupported_value", {
  613. 'schema': '{"contentEncoding": "x-token"}',
  614. 'access': 'contentEncoding',
  615. 'raises': NotImplementedError(
  616. "contentEncoding value 'x-token' is not supported")
  617. }),
  618. ("contentEncoding_unknown_value", {
  619. 'schema': '{"contentEncoding": "bogus"}',
  620. 'access': 'contentEncoding',
  621. 'raises': SchemaError(
  622. "contentEncoding value 'bogus' is not valid")
  623. }),
  624. ("divisibleBy_default", {
  625. 'schema': '{}',
  626. 'expected': {
  627. 'divisibleBy': 1
  628. }
  629. }),
  630. ("divisibleBy_int", {
  631. 'schema': '{"divisibleBy": 5}',
  632. 'expected': {
  633. 'divisibleBy': 5
  634. }
  635. }),
  636. ("divisibleBy_float", {
  637. 'schema': '{"divisibleBy": 3.5}',
  638. 'expected': {
  639. 'divisibleBy': 3.5
  640. }
  641. }),
  642. ("divisibleBy_wrong_type", {
  643. 'schema': '{"divisibleBy": "foobar"}',
  644. 'access': 'divisibleBy',
  645. 'raises': SchemaError(
  646. "divisibleBy value 'foobar' is not a numeric type")
  647. }),
  648. ("divisibleBy_minus_one", {
  649. 'schema': '{"divisibleBy": -1}',
  650. 'access': 'divisibleBy',
  651. 'raises': SchemaError(
  652. "divisibleBy value -1 cannot be negative")
  653. }),
  654. ('disallow_default', {
  655. 'schema': '{}',
  656. 'expected': {
  657. 'disallow': None
  658. },
  659. }),
  660. ('disallow_string', {
  661. 'schema': '{"disallow": "string"}',
  662. 'expected': {
  663. 'disallow': ['string']
  664. },
  665. }),
  666. ('disallow_number', {
  667. 'schema': '{"disallow": "number"}',
  668. 'expected': {
  669. 'disallow': ['number']
  670. },
  671. }),
  672. ('disallow_integer', {
  673. 'schema': '{"disallow": "integer"}',
  674. 'expected': {
  675. 'disallow': ['integer']
  676. },
  677. }),
  678. ('disallow_boolean', {
  679. 'schema': '{"disallow": "boolean"}',
  680. 'expected': {
  681. 'disallow': ['boolean']
  682. },
  683. }),
  684. ('disallow_object', {
  685. 'schema': '{"disallow": "object"}',
  686. 'expected': {
  687. 'disallow': ['object']
  688. },
  689. }),
  690. ('disallow_array', {
  691. 'schema': '{"disallow": "array"}',
  692. 'expected': {
  693. 'disallow': ['array']
  694. },
  695. }),
  696. ('disallow_complex_subtype', {
  697. 'schema': '{"disallow": {}}',
  698. 'expected': {
  699. 'disallow': [{}],
  700. },
  701. }),
  702. ('disallow_list', {
  703. 'schema': '{"disallow": ["string", "number"]}',
  704. 'expected': {
  705. 'disallow': ["string", "number"],
  706. },
  707. }),
  708. ('disallow_wrong_type', {
  709. 'schema': '{"disallow": 5}',
  710. 'access': 'disallow',
  711. 'raises': SchemaError(
  712. "disallow value 5 is not a simple type name,"
  713. " nested schema nor a list of those"),
  714. }),
  715. ('disallow_not_a_simple_disallow_name', {
  716. 'schema': '{"disallow": "foobar"}',
  717. 'access': 'disallow',
  718. 'raises': SchemaError(
  719. "disallow value 'foobar' is not a simple type name")
  720. }),
  721. ('disallow_list_duplicates', {
  722. 'schema': '{"disallow": ["string", "string"]}',
  723. 'access': 'disallow',
  724. 'raises': SchemaError(
  725. "disallow value ['string', 'string'] contains"
  726. " duplicate element 'string'")
  727. }),
  728. ('extends_not_supported', {
  729. 'schema': '{}',
  730. 'access': 'extends',
  731. 'raises': NotImplementedError(
  732. "extends property is not supported"),
  733. }),
  734. ('default_with_value', {
  735. 'schema': '{"default": 5}',
  736. 'expected': {
  737. 'default': 5
  738. }
  739. }),
  740. ('default_without_value', {
  741. 'schema': '{}',
  742. 'access': 'default',
  743. 'raises': SchemaError("There is no schema default for this item"),
  744. }),
  745. ]
  746. def test_schema_attribute(self):
  747. schema = Schema(simplejson.loads(self.schema))
  748. if hasattr(self, 'expected'):
  749. for attr, expected_value in self.expected.iteritems():
  750. self.assertEqual(
  751. expected_value, getattr(schema, attr))
  752. elif hasattr(self, 'access') and hasattr(self, 'raises'):
  753. self.assertRaises(
  754. type(self.raises),
  755. getattr, schema, self.access)
  756. try:
  757. getattr(schema, self.access)
  758. except type(self.raises) as ex:
  759. self.assertEqual(str(ex), str(self.raises))
  760. except Exception as ex:
  761. self.fail("Raised exception {0!r} instead of {1!r}".format(
  762. ex, self.raises))
  763. else:
  764. self.fail("Broken test definition, must define 'expected' "
  765. "or 'access' and 'raises' scenario attributes")