test_validation.py 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579
  1. # -*- coding: utf-8 -*-
  2. import re
  3. import sys
  4. from datetime import datetime, date
  5. from random import choice
  6. from string import ascii_lowercase
  7. from pytest import mark
  8. from cerberus import errors, Validator
  9. from cerberus.tests import (
  10. assert_bad_type, assert_document_error, assert_fail, assert_has_error,
  11. assert_not_has_error, assert_success
  12. )
  13. from cerberus.tests.conftest import sample_schema
  14. def test_empty_document():
  15. assert_document_error(None, sample_schema, None,
  16. errors.DOCUMENT_MISSING)
  17. def test_bad_document_type():
  18. document = "not a dict"
  19. assert_document_error(
  20. document, sample_schema, None,
  21. errors.DOCUMENT_FORMAT.format(document)
  22. )
  23. def test_unknown_field(validator):
  24. field = 'surname'
  25. assert_fail({field: 'doe'}, validator=validator,
  26. error=(field, (), errors.UNKNOWN_FIELD, None))
  27. assert validator.errors == {field: ['unknown field']}
  28. def test_empty_field_definition(document):
  29. field = 'name'
  30. schema = {field: {}}
  31. assert_success(document, schema)
  32. def test_required_field(schema):
  33. field = 'a_required_string'
  34. required_string_extension = {
  35. 'a_required_string': {'type': 'string',
  36. 'minlength': 2,
  37. 'maxlength': 10,
  38. 'required': True}}
  39. schema.update(required_string_extension)
  40. assert_fail({'an_integer': 1}, schema,
  41. error=(field, (field, 'required'), errors.REQUIRED_FIELD,
  42. True))
  43. def test_nullable_field():
  44. assert_success({'a_nullable_integer': None})
  45. assert_success({'a_nullable_integer': 3})
  46. assert_success({'a_nullable_field_without_type': None})
  47. assert_fail({'a_nullable_integer': "foo"})
  48. assert_fail({'an_integer': None})
  49. assert_fail({'a_not_nullable_field_without_type': None})
  50. def test_readonly_field():
  51. field = 'a_readonly_string'
  52. assert_fail({field: 'update me if you can'},
  53. error=(field, (field, 'readonly'), errors.READONLY_FIELD, True))
  54. def test_readonly_field_first_rule():
  55. # test that readonly rule is checked before any other rule, and blocks.
  56. # See #63.
  57. schema = {
  58. 'a_readonly_number': {
  59. 'type': 'integer',
  60. 'readonly': True,
  61. 'max': 1
  62. }
  63. }
  64. v = Validator(schema)
  65. v.validate({'a_readonly_number': 2})
  66. # it would be a list if there's more than one error; we get a dict
  67. # instead.
  68. assert 'read-only' in v.errors['a_readonly_number'][0]
  69. def test_readonly_field_with_default_value():
  70. schema = {
  71. 'created': {
  72. 'type': 'string',
  73. 'readonly': True,
  74. 'default': 'today'
  75. },
  76. 'modified': {
  77. 'type': 'string',
  78. 'readonly': True,
  79. 'default_setter': lambda d: d['created']
  80. }
  81. }
  82. assert_success({}, schema)
  83. expected_errors = [('created', ('created', 'readonly'),
  84. errors.READONLY_FIELD,
  85. schema['created']['readonly']),
  86. ('modified', ('modified', 'readonly'),
  87. errors.READONLY_FIELD,
  88. schema['modified']['readonly'])]
  89. assert_fail({'created': 'tomorrow', 'modified': 'today'},
  90. schema, errors=expected_errors)
  91. assert_fail({'created': 'today', 'modified': 'today'},
  92. schema, errors=expected_errors)
  93. def test_nested_readonly_field_with_default_value():
  94. schema = {
  95. 'some_field': {
  96. 'type': 'dict',
  97. 'schema': {
  98. 'created': {
  99. 'type': 'string',
  100. 'readonly': True,
  101. 'default': 'today'
  102. },
  103. 'modified': {
  104. 'type': 'string',
  105. 'readonly': True,
  106. 'default_setter': lambda d: d['created']
  107. }
  108. }
  109. }
  110. }
  111. assert_success({'some_field': {}}, schema)
  112. expected_errors = [
  113. (('some_field', 'created'),
  114. ('some_field', 'schema', 'created', 'readonly'),
  115. errors.READONLY_FIELD,
  116. schema['some_field']['schema']['created']['readonly']),
  117. (('some_field', 'modified'),
  118. ('some_field', 'schema', 'modified', 'readonly'),
  119. errors.READONLY_FIELD,
  120. schema['some_field']['schema']['modified']['readonly'])]
  121. assert_fail({'some_field': {'created': 'tomorrow', 'modified': 'now'}},
  122. schema, errors=expected_errors)
  123. assert_fail({'some_field': {'created': 'today', 'modified': 'today'}},
  124. schema, errors=expected_errors)
  125. def test_repeated_readonly(validator):
  126. # https://github.com/pyeve/cerberus/issues/311
  127. validator.schema = {'id': {'readonly': True}}
  128. assert_fail({'id': 0}, validator=validator)
  129. assert_fail({'id': 0}, validator=validator)
  130. def test_not_a_string():
  131. assert_bad_type('a_string', 'string', 1)
  132. def test_not_a_binary():
  133. # 'u' literal prefix produces type `str` in Python 3
  134. assert_bad_type('a_binary', 'binary', u"i'm not a binary")
  135. def test_not_a_integer():
  136. assert_bad_type('an_integer', 'integer', "i'm not an integer")
  137. def test_not_a_boolean():
  138. assert_bad_type('a_boolean', 'boolean', "i'm not a boolean")
  139. def test_not_a_datetime():
  140. assert_bad_type('a_datetime', 'datetime', "i'm not a datetime")
  141. def test_not_a_float():
  142. assert_bad_type('a_float', 'float', "i'm not a float")
  143. def test_not_a_number():
  144. assert_bad_type('a_number', 'number', "i'm not a number")
  145. def test_not_a_list():
  146. assert_bad_type('a_list_of_values', 'list', "i'm not a list")
  147. def test_not_a_dict():
  148. assert_bad_type('a_dict', 'dict', "i'm not a dict")
  149. def test_bad_max_length(schema):
  150. field = 'a_string'
  151. max_length = schema[field]['maxlength']
  152. value = "".join(choice(ascii_lowercase) for i in range(max_length + 1))
  153. assert_fail({field: value},
  154. error=(field, (field, 'maxlength'), errors.MAX_LENGTH,
  155. max_length, (len(value),)))
  156. def test_bad_max_length_binary(schema):
  157. field = 'a_binary'
  158. max_length = schema[field]['maxlength']
  159. value = b'\x00' * (max_length + 1)
  160. assert_fail({field: value},
  161. error=(field, (field, 'maxlength'), errors.MAX_LENGTH,
  162. max_length, (len(value),)))
  163. def test_bad_min_length(schema):
  164. field = 'a_string'
  165. min_length = schema[field]['minlength']
  166. value = "".join(choice(ascii_lowercase) for i in range(min_length - 1))
  167. assert_fail({field: value},
  168. error=(field, (field, 'minlength'), errors.MIN_LENGTH,
  169. min_length, (len(value),)))
  170. def test_bad_min_length_binary(schema):
  171. field = 'a_binary'
  172. min_length = schema[field]['minlength']
  173. value = b'\x00' * (min_length - 1)
  174. assert_fail({field: value},
  175. error=(field, (field, 'minlength'), errors.MIN_LENGTH,
  176. min_length, (len(value),)))
  177. def test_bad_max_value(schema):
  178. def assert_bad_max_value(field, inc):
  179. max_value = schema[field]['max']
  180. value = max_value + inc
  181. assert_fail({field: value},
  182. error=(field, (field, 'max'), errors.MAX_VALUE, max_value))
  183. field = 'an_integer'
  184. assert_bad_max_value(field, 1)
  185. field = 'a_float'
  186. assert_bad_max_value(field, 1.0)
  187. field = 'a_number'
  188. assert_bad_max_value(field, 1)
  189. def test_bad_min_value(schema):
  190. def assert_bad_min_value(field, inc):
  191. min_value = schema[field]['min']
  192. value = min_value - inc
  193. assert_fail({field: value},
  194. error=(field, (field, 'min'),
  195. errors.MIN_VALUE, min_value))
  196. field = 'an_integer'
  197. assert_bad_min_value(field, 1)
  198. field = 'a_float'
  199. assert_bad_min_value(field, 1.0)
  200. field = 'a_number'
  201. assert_bad_min_value(field, 1)
  202. def test_bad_schema():
  203. field = 'a_dict'
  204. subschema_field = 'address'
  205. schema = {field: {'type': 'dict',
  206. 'schema': {subschema_field: {'type': 'string'},
  207. 'city': {'type': 'string', 'required': True}}
  208. }}
  209. document = {field: {subschema_field: 34}}
  210. validator = Validator(schema)
  211. assert_fail(
  212. document, validator=validator,
  213. error=(field, (field, 'schema'), errors.MAPPING_SCHEMA,
  214. validator.schema['a_dict']['schema']),
  215. child_errors=[
  216. ((field, subschema_field),
  217. (field, 'schema', subschema_field, 'type'),
  218. errors.BAD_TYPE, 'string'),
  219. ((field, 'city'), (field, 'schema', 'city', 'required'),
  220. errors.REQUIRED_FIELD, True)]
  221. )
  222. handler = errors.BasicErrorHandler
  223. assert field in validator.errors
  224. assert subschema_field in validator.errors[field][-1]
  225. assert handler.messages[errors.BAD_TYPE.code].format(constraint='string') \
  226. in validator.errors[field][-1][subschema_field]
  227. assert 'city' in validator.errors[field][-1]
  228. assert (handler.messages[errors.REQUIRED_FIELD.code]
  229. in validator.errors[field][-1]['city'])
  230. def test_bad_valueschema():
  231. field = 'a_dict_with_valueschema'
  232. schema_field = 'a_string'
  233. value = {schema_field: 'not an integer'}
  234. exp_child_errors = [
  235. ((field, schema_field), (field, 'valueschema', 'type'), errors.BAD_TYPE,
  236. 'integer')]
  237. assert_fail({field: value},
  238. error=(field, (field, 'valueschema'), errors.VALUESCHEMA,
  239. {'type': 'integer'}), child_errors=exp_child_errors)
  240. def test_bad_list_of_values(validator):
  241. field = 'a_list_of_values'
  242. value = ['a string', 'not an integer']
  243. assert_fail({field: value}, validator=validator,
  244. error=(field, (field, 'items'), errors.BAD_ITEMS,
  245. [{'type': 'string'}, {'type': 'integer'}]),
  246. child_errors=[((field, 1), (field, 'items', 1, 'type'),
  247. errors.BAD_TYPE, 'integer')])
  248. assert (errors.BasicErrorHandler.messages[errors.BAD_TYPE.code].
  249. format(constraint='integer')
  250. in validator.errors[field][-1][1])
  251. value = ['a string', 10, 'an extra item']
  252. assert_fail({field: value},
  253. error=(field, (field, 'items'), errors.ITEMS_LENGTH,
  254. [{'type': 'string'}, {'type': 'integer'}], (2, 3)))
  255. def test_bad_list_of_integers():
  256. field = 'a_list_of_integers'
  257. value = [34, 'not an integer']
  258. assert_fail({field: value})
  259. def test_bad_list_of_dicts():
  260. field = 'a_list_of_dicts'
  261. map_schema = {'sku': {'type': 'string'},
  262. 'price': {'type': 'integer', 'required': True}}
  263. seq_schema = {'type': 'dict', 'schema': map_schema}
  264. schema = {field: {'type': 'list', 'schema': seq_schema}}
  265. validator = Validator(schema)
  266. value = [{'sku': 'KT123', 'price': '100'}]
  267. document = {field: value}
  268. assert_fail(document, validator=validator,
  269. error=(field, (field, 'schema'), errors.SEQUENCE_SCHEMA,
  270. seq_schema),
  271. child_errors=[((field, 0), (field, 'schema', 'schema'),
  272. errors.MAPPING_SCHEMA, map_schema)])
  273. assert field in validator.errors
  274. assert 0 in validator.errors[field][-1]
  275. assert 'price' in validator.errors[field][-1][0][-1]
  276. exp_msg = errors.BasicErrorHandler.messages[errors.BAD_TYPE.code] \
  277. .format(constraint='integer')
  278. assert exp_msg in validator.errors[field][-1][0][-1]['price']
  279. value = ["not a dict"]
  280. exp_child_errors = [((field, 0), (field, 'schema', 'type'),
  281. errors.BAD_TYPE, 'dict', ())]
  282. assert_fail({field: value},
  283. error=(field, (field, 'schema'), errors.SEQUENCE_SCHEMA,
  284. seq_schema),
  285. child_errors=exp_child_errors)
  286. def test_array_unallowed():
  287. field = 'an_array'
  288. value = ['agent', 'client', 'profit']
  289. assert_fail({field: value},
  290. error=(field, (field, 'allowed'), errors.UNALLOWED_VALUES,
  291. ['agent', 'client', 'vendor'], ['profit']))
  292. def test_string_unallowed():
  293. field = 'a_restricted_string'
  294. value = 'profit'
  295. assert_fail({field: value},
  296. error=(field, (field, 'allowed'), errors.UNALLOWED_VALUE,
  297. ['agent', 'client', 'vendor'], value))
  298. def test_integer_unallowed():
  299. field = 'a_restricted_integer'
  300. value = 2
  301. assert_fail({field: value},
  302. error=(field, (field, 'allowed'), errors.UNALLOWED_VALUE,
  303. [-1, 0, 1], value))
  304. def test_integer_allowed():
  305. assert_success({'a_restricted_integer': -1})
  306. def test_validate_update():
  307. assert_success({'an_integer': 100,
  308. 'a_dict': {'address': 'adr'},
  309. 'a_list_of_dicts': [{'sku': 'let'}]
  310. }, update=True)
  311. def test_string():
  312. assert_success({'a_string': 'john doe'})
  313. def test_string_allowed():
  314. assert_success({'a_restricted_string': 'client'})
  315. def test_integer():
  316. assert_success({'an_integer': 50})
  317. def test_boolean():
  318. assert_success({'a_boolean': True})
  319. def test_datetime():
  320. assert_success({'a_datetime': datetime.now()})
  321. def test_float():
  322. assert_success({'a_float': 3.5})
  323. assert_success({'a_float': 1})
  324. def test_number():
  325. assert_success({'a_number': 3.5})
  326. assert_success({'a_number': 3})
  327. def test_array():
  328. assert_success({'an_array': ['agent', 'client']})
  329. def test_set():
  330. assert_success({'a_set': set(['hello', 1])})
  331. def test_one_of_two_types(validator):
  332. field = 'one_or_more_strings'
  333. assert_success({field: 'foo'})
  334. assert_success({field: ['foo', 'bar']})
  335. exp_child_errors = [((field, 1), (field, 'schema', 'type'),
  336. errors.BAD_TYPE, 'string')]
  337. assert_fail({field: ['foo', 23]}, validator=validator,
  338. error=(field, (field, 'schema'), errors.SEQUENCE_SCHEMA,
  339. {'type': 'string'}),
  340. child_errors=exp_child_errors)
  341. assert_fail({field: 23},
  342. error=((field,), (field, 'type'), errors.BAD_TYPE,
  343. ['string', 'list']))
  344. assert validator.errors == {field: [{1: ['must be of string type']}]}
  345. def test_regex(validator):
  346. field = 'a_regex_email'
  347. assert_success({field: 'valid.email@gmail.com'}, validator=validator)
  348. assert_fail({field: 'invalid'}, update=True,
  349. error=(field, (field, 'regex'), errors.REGEX_MISMATCH,
  350. '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'))
  351. def test_a_list_of_dicts():
  352. assert_success(
  353. {
  354. 'a_list_of_dicts': [
  355. {'sku': 'AK345', 'price': 100},
  356. {'sku': 'YZ069', 'price': 25}
  357. ]
  358. }
  359. )
  360. def test_a_list_of_values():
  361. assert_success({'a_list_of_values': ['hello', 100]})
  362. def test_a_list_of_integers():
  363. assert_success({'a_list_of_integers': [99, 100]})
  364. def test_a_dict(schema):
  365. assert_success({'a_dict': {'address': 'i live here',
  366. 'city': 'in my own town'}})
  367. assert_fail(
  368. {'a_dict': {'address': 8545}},
  369. error=('a_dict', ('a_dict', 'schema'), errors.MAPPING_SCHEMA,
  370. schema['a_dict']['schema']),
  371. child_errors=[(('a_dict', 'address'),
  372. ('a_dict', 'schema', 'address', 'type'),
  373. errors.BAD_TYPE, 'string'),
  374. (('a_dict', 'city'),
  375. ('a_dict', 'schema', 'city', 'required'),
  376. errors.REQUIRED_FIELD, True)]
  377. )
  378. def test_a_dict_with_valueschema(validator):
  379. assert_success({'a_dict_with_valueschema':
  380. {'an integer': 99, 'another integer': 100}})
  381. error = (
  382. 'a_dict_with_valueschema', ('a_dict_with_valueschema', 'valueschema'),
  383. errors.VALUESCHEMA, {'type': 'integer'})
  384. child_errors = [
  385. (('a_dict_with_valueschema', 'a string'),
  386. ('a_dict_with_valueschema', 'valueschema', 'type'),
  387. errors.BAD_TYPE, 'integer')]
  388. assert_fail({'a_dict_with_valueschema': {'a string': '99'}},
  389. validator=validator, error=error, child_errors=child_errors)
  390. assert 'valueschema' in \
  391. validator.schema_error_tree['a_dict_with_valueschema']
  392. v = validator.schema_error_tree
  393. assert len(v['a_dict_with_valueschema']['valueschema'].descendants) == 1
  394. def test_a_dict_with_keyschema():
  395. assert_success({'a_dict_with_keyschema': {'key': 'value'}})
  396. assert_fail({'a_dict_with_keyschema': {'KEY': 'value'}})
  397. def test_a_list_length(schema):
  398. field = 'a_list_length'
  399. min_length = schema[field]['minlength']
  400. max_length = schema[field]['maxlength']
  401. assert_fail({field: [1] * (min_length - 1)},
  402. error=(field, (field, 'minlength'), errors.MIN_LENGTH,
  403. min_length, (min_length - 1,)))
  404. for i in range(min_length, max_length):
  405. value = [1] * i
  406. assert_success({field: value})
  407. assert_fail({field: [1] * (max_length + 1)},
  408. error=(field, (field, 'maxlength'), errors.MAX_LENGTH,
  409. max_length, (max_length + 1,)))
  410. def test_custom_datatype():
  411. class MyValidator(Validator):
  412. def _validate_type_objectid(self, value):
  413. if re.match('[a-f0-9]{24}', value):
  414. return True
  415. schema = {'test_field': {'type': 'objectid'}}
  416. validator = MyValidator(schema)
  417. assert_success({'test_field': '50ad188438345b1049c88a28'},
  418. validator=validator)
  419. assert_fail({'test_field': 'hello'}, validator=validator,
  420. error=('test_field', ('test_field', 'type'), errors.BAD_TYPE,
  421. 'objectid'))
  422. def test_custom_datatype_rule():
  423. class MyValidator(Validator):
  424. def _validate_min_number(self, min_number, field, value):
  425. """ {'type': 'number'} """
  426. if value < min_number:
  427. self._error(field, 'Below the min')
  428. # TODO replace with TypeDefintion in next major release
  429. def _validate_type_number(self, value):
  430. if isinstance(value, int):
  431. return True
  432. schema = {'test_field': {'min_number': 1, 'type': 'number'}}
  433. validator = MyValidator(schema)
  434. assert_fail({'test_field': '0'}, validator=validator,
  435. error=('test_field', ('test_field', 'type'), errors.BAD_TYPE,
  436. 'number'))
  437. assert_fail({'test_field': 0}, validator=validator,
  438. error=('test_field', (), errors.CUSTOM, None,
  439. ('Below the min',)))
  440. assert validator.errors == {'test_field': ['Below the min']}
  441. def test_custom_validator():
  442. class MyValidator(Validator):
  443. def _validate_isodd(self, isodd, field, value):
  444. """ {'type': 'boolean'} """
  445. if isodd and not bool(value & 1):
  446. self._error(field, 'Not an odd number')
  447. schema = {'test_field': {'isodd': True}}
  448. validator = MyValidator(schema)
  449. assert_success({'test_field': 7}, validator=validator)
  450. assert_fail({'test_field': 6}, validator=validator,
  451. error=('test_field', (), errors.CUSTOM, None,
  452. ('Not an odd number',)))
  453. assert validator.errors == {'test_field': ['Not an odd number']}
  454. @mark.parametrize('value, _type',
  455. (('', 'string'), ((), 'list'), ({}, 'dict'), ([], 'list')))
  456. def test_empty_values(value, _type):
  457. field = 'test'
  458. schema = {field: {'type': _type}}
  459. document = {field: value}
  460. assert_success(document, schema)
  461. schema[field]['empty'] = False
  462. assert_fail(document, schema,
  463. error=(field, (field, 'empty'),
  464. errors.EMPTY_NOT_ALLOWED, False))
  465. schema[field]['empty'] = True
  466. assert_success(document, schema)
  467. def test_empty_skips_regex(validator):
  468. schema = {'foo': {'empty': True, 'regex': r'\d?\d\.\d\d',
  469. 'type': 'string'}}
  470. assert validator({'foo': ''}, schema)
  471. def test_ignore_none_values():
  472. field = 'test'
  473. schema = {field: {'type': 'string', 'empty': False, 'required': False}}
  474. document = {field: None}
  475. # Test normal behaviour
  476. validator = Validator(schema, ignore_none_values=False)
  477. assert_fail(document, validator=validator)
  478. validator.schema[field]['required'] = True
  479. validator.schema.validate()
  480. _errors = assert_fail(document, validator=validator)
  481. assert_not_has_error(_errors, field, (field, 'required'),
  482. errors.REQUIRED_FIELD, True)
  483. # Test ignore None behaviour
  484. validator = Validator(schema, ignore_none_values=True)
  485. validator.schema[field]['required'] = False
  486. validator.schema.validate()
  487. assert_success(document, validator=validator)
  488. validator.schema[field]['required'] = True
  489. _errors = assert_fail(schema=schema, document=document, validator=validator)
  490. assert_has_error(_errors, field, (field, 'required'), errors.REQUIRED_FIELD,
  491. True)
  492. assert_not_has_error(_errors, field, (field, 'type'), errors.BAD_TYPE,
  493. 'string')
  494. def test_unknown_keys():
  495. schema = {}
  496. # test that unknown fields are allowed when allow_unknown is True.
  497. v = Validator(allow_unknown=True, schema=schema)
  498. assert_success({"unknown1": True, "unknown2": "yes"}, validator=v)
  499. # test that unknown fields are allowed only if they meet the
  500. # allow_unknown schema when provided.
  501. v.allow_unknown = {'type': 'string'}
  502. assert_success(document={'name': 'mark'}, validator=v)
  503. assert_fail({"name": 1}, validator=v)
  504. # test that unknown fields are not allowed if allow_unknown is False
  505. v.allow_unknown = False
  506. assert_fail({'name': 'mark'}, validator=v)
  507. def test_unknown_key_dict(validator):
  508. # https://github.com/pyeve/cerberus/issues/177
  509. validator.allow_unknown = True
  510. document = {'a_dict': {'foo': 'foo_value', 'bar': 25}}
  511. assert_success(document, {}, validator=validator)
  512. def test_unknown_key_list(validator):
  513. # https://github.com/pyeve/cerberus/issues/177
  514. validator.allow_unknown = True
  515. document = {'a_dict': ['foo', 'bar']}
  516. assert_success(document, {}, validator=validator)
  517. def test_unknown_keys_list_of_dicts(validator):
  518. # test that allow_unknown is honored even for subdicts in lists.
  519. # https://github.com/pyeve/cerberus/issues/67.
  520. validator.allow_unknown = True
  521. document = {'a_list_of_dicts': [{'sku': 'YZ069', 'price': 25,
  522. 'extra': True}]}
  523. assert_success(document, validator=validator)
  524. def test_unknown_keys_retain_custom_rules():
  525. # test that allow_unknown schema respect custom validation rules.
  526. # https://github.com/pyeve/cerberus/issues/#66.
  527. class CustomValidator(Validator):
  528. def _validate_type_foo(self, value):
  529. if value == "foo":
  530. return True
  531. validator = CustomValidator({})
  532. validator.allow_unknown = {"type": "foo"}
  533. assert_success(document={"fred": "foo", "barney": "foo"},
  534. validator=validator)
  535. def test_nested_unknown_keys():
  536. schema = {
  537. 'field1': {
  538. 'type': 'dict',
  539. 'allow_unknown': True,
  540. 'schema': {'nested1': {'type': 'string'}}
  541. }
  542. }
  543. document = {
  544. 'field1': {
  545. 'nested1': 'foo',
  546. 'arb1': 'bar',
  547. 'arb2': 42
  548. }
  549. }
  550. assert_success(document=document, schema=schema)
  551. schema['field1']['allow_unknown'] = {'type': 'string'}
  552. assert_fail(document=document, schema=schema)
  553. def test_novalidate_noerrors(validator):
  554. """
  555. In v0.1.0 and below `self.errors` raised an exception if no
  556. validation had been performed yet.
  557. """
  558. assert validator.errors == {}
  559. def test_callable_validator():
  560. """
  561. Validator instance is callable, functions as a shorthand
  562. passthrough to validate()
  563. """
  564. schema = {'test_field': {'type': 'string'}}
  565. v = Validator(schema)
  566. assert v.validate({'test_field': 'foo'})
  567. assert v({'test_field': 'foo'})
  568. assert not v.validate({'test_field': 1})
  569. assert not v({'test_field': 1})
  570. def test_dependencies_field():
  571. schema = {'test_field': {'dependencies': 'foo'},
  572. 'foo': {'type': 'string'}}
  573. assert_success({'test_field': 'foobar', 'foo': 'bar'}, schema)
  574. assert_fail({'test_field': 'foobar'}, schema)
  575. def test_dependencies_list():
  576. schema = {
  577. 'test_field': {'dependencies': ['foo', 'bar']},
  578. 'foo': {'type': 'string'},
  579. 'bar': {'type': 'string'}
  580. }
  581. assert_success({'test_field': 'foobar', 'foo': 'bar', 'bar': 'foo'},
  582. schema)
  583. assert_fail({'test_field': 'foobar', 'foo': 'bar'}, schema)
  584. def test_dependencies_list_with_required_field():
  585. schema = {
  586. 'test_field': {'required': True, 'dependencies': ['foo', 'bar']},
  587. 'foo': {'type': 'string'},
  588. 'bar': {'type': 'string'}
  589. }
  590. # False: all dependencies missing
  591. assert_fail({'test_field': 'foobar'}, schema)
  592. # False: one of dependencies missing
  593. assert_fail({'test_field': 'foobar', 'foo': 'bar'}, schema)
  594. # False: one of dependencies missing
  595. assert_fail({'test_field': 'foobar', 'bar': 'foo'}, schema)
  596. # False: dependencies are validated and field is required
  597. assert_fail({'foo': 'bar', 'bar': 'foo'}, schema)
  598. # False: All dependencies are optional but field is still required
  599. assert_fail({}, schema)
  600. # True: dependency missing
  601. assert_fail({'foo': 'bar'}, schema)
  602. # True: dependencies are validated but field is not required
  603. schema['test_field']['required'] = False
  604. assert_success({'foo': 'bar', 'bar': 'foo'}, schema)
  605. def test_dependencies_list_with_subodcuments_fields():
  606. schema = {
  607. 'test_field': {'dependencies': ['a_dict.foo', 'a_dict.bar']},
  608. 'a_dict': {
  609. 'type': 'dict',
  610. 'schema': {
  611. 'foo': {'type': 'string'},
  612. 'bar': {'type': 'string'}
  613. }
  614. }
  615. }
  616. assert_success({'test_field': 'foobar',
  617. 'a_dict': {'foo': 'foo', 'bar': 'bar'}}, schema)
  618. assert_fail({'test_field': 'foobar', 'a_dict': {}}, schema)
  619. assert_fail({'test_field': 'foobar',
  620. 'a_dict': {'foo': 'foo'}}, schema)
  621. def test_dependencies_dict():
  622. schema = {
  623. 'test_field': {'dependencies': {'foo': 'foo', 'bar': 'bar'}},
  624. 'foo': {'type': 'string'},
  625. 'bar': {'type': 'string'}
  626. }
  627. assert_success({'test_field': 'foobar', 'foo': 'foo', 'bar': 'bar'},
  628. schema)
  629. assert_fail({'test_field': 'foobar', 'foo': 'foo'}, schema)
  630. assert_fail({'test_field': 'foobar', 'foo': 'bar'}, schema)
  631. assert_fail({'test_field': 'foobar', 'bar': 'bar'}, schema)
  632. assert_fail({'test_field': 'foobar', 'bar': 'foo'}, schema)
  633. assert_fail({'test_field': 'foobar'}, schema)
  634. def test_dependencies_dict_with_required_field():
  635. schema = {
  636. 'test_field': {
  637. 'required': True,
  638. 'dependencies': {'foo': 'foo', 'bar': 'bar'}
  639. },
  640. 'foo': {'type': 'string'},
  641. 'bar': {'type': 'string'}
  642. }
  643. # False: all dependencies missing
  644. assert_fail({'test_field': 'foobar'}, schema)
  645. # False: one of dependencies missing
  646. assert_fail({'test_field': 'foobar', 'foo': 'foo'}, schema)
  647. assert_fail({'test_field': 'foobar', 'bar': 'bar'}, schema)
  648. # False: dependencies are validated and field is required
  649. assert_fail({'foo': 'foo', 'bar': 'bar'}, schema)
  650. # False: All dependencies are optional, but field is still required
  651. assert_fail({}, schema)
  652. # False: dependency missing
  653. assert_fail({'foo': 'bar'}, schema)
  654. assert_success({'test_field': 'foobar', 'foo': 'foo', 'bar': 'bar'},
  655. schema)
  656. # True: dependencies are validated but field is not required
  657. schema['test_field']['required'] = False
  658. assert_success({'foo': 'bar', 'bar': 'foo'}, schema)
  659. def test_dependencies_field_satisfy_nullable_field():
  660. # https://github.com/pyeve/cerberus/issues/305
  661. schema = {
  662. 'foo': {'nullable': True},
  663. 'bar': {'dependencies': 'foo'}
  664. }
  665. assert_success({'foo': None, 'bar': 1}, schema)
  666. assert_success({'foo': None}, schema)
  667. assert_fail({'bar': 1}, schema)
  668. def test_dependencies_field_with_mutually_dependent_nullable_fields():
  669. # https://github.com/pyeve/cerberus/pull/306
  670. schema = {
  671. 'foo': {'dependencies': 'bar', 'nullable': True},
  672. 'bar': {'dependencies': 'foo', 'nullable': True}
  673. }
  674. assert_success({'foo': None, 'bar': None}, schema)
  675. assert_success({'foo': 1, 'bar': 1}, schema)
  676. assert_success({'foo': None, 'bar': 1}, schema)
  677. assert_fail({'foo': None}, schema)
  678. assert_fail({'foo': 1}, schema)
  679. def test_dependencies_dict_with_subdocuments_fields():
  680. schema = {
  681. 'test_field': {'dependencies': {'a_dict.foo': ['foo', 'bar'],
  682. 'a_dict.bar': 'bar'}},
  683. 'a_dict': {
  684. 'type': 'dict',
  685. 'schema': {
  686. 'foo': {'type': 'string'},
  687. 'bar': {'type': 'string'}
  688. }
  689. }
  690. }
  691. assert_success({'test_field': 'foobar',
  692. 'a_dict': {'foo': 'foo', 'bar': 'bar'}}, schema)
  693. assert_success({'test_field': 'foobar',
  694. 'a_dict': {'foo': 'bar', 'bar': 'bar'}}, schema)
  695. assert_fail({'test_field': 'foobar', 'a_dict': {}}, schema)
  696. assert_fail({'test_field': 'foobar',
  697. 'a_dict': {'foo': 'foo', 'bar': 'foo'}}, schema)
  698. assert_fail({'test_field': 'foobar', 'a_dict': {'bar': 'foo'}},
  699. schema)
  700. assert_fail({'test_field': 'foobar', 'a_dict': {'bar': 'bar'}},
  701. schema)
  702. def test_root_relative_dependencies():
  703. # https://github.com/pyeve/cerberus/issues/288
  704. subschema = {'version': {'dependencies': '^repo'}}
  705. schema = {'package': {'allow_unknown': True, 'schema': subschema},
  706. 'repo': {}}
  707. assert_fail(
  708. {'package': {'repo': 'somewhere', 'version': 0}}, schema,
  709. error=('package', ('package', 'schema'),
  710. errors.MAPPING_SCHEMA, subschema),
  711. child_errors=[(
  712. ('package', 'version'),
  713. ('package', 'schema', 'version', 'dependencies'),
  714. errors.DEPENDENCIES_FIELD, '^repo', ('^repo',)
  715. )]
  716. )
  717. assert_success({'repo': 'somewhere', 'package': {'version': 1}}, schema)
  718. def test_dependencies_errors():
  719. v = Validator({'field1': {'required': False},
  720. 'field2': {'required': True,
  721. 'dependencies': {'field1': ['one', 'two']}}})
  722. assert_fail({'field1': 'three', 'field2': 7}, validator=v,
  723. error=('field2', ('field2', 'dependencies'),
  724. errors.DEPENDENCIES_FIELD_VALUE,
  725. {'field1': ['one', 'two']}, ({'field1': 'three'},)))
  726. def test_options_passed_to_nested_validators(validator):
  727. validator.schema = {'sub_dict': {'type': 'dict',
  728. 'schema': {'foo': {'type': 'string'}}}}
  729. validator.allow_unknown = True
  730. assert_success({'sub_dict': {'foo': 'bar', 'unknown': True}},
  731. validator=validator)
  732. def test_self_root_document():
  733. """ Make sure self.root_document is always the root document.
  734. See:
  735. * https://github.com/pyeve/cerberus/pull/42
  736. * https://github.com/pyeve/eve/issues/295
  737. """
  738. class MyValidator(Validator):
  739. def _validate_root_doc(self, root_doc, field, value):
  740. """ {'type': 'boolean'} """
  741. if ('sub' not in self.root_document or
  742. len(self.root_document['sub']) != 2):
  743. self._error(field, 'self.context is not the root doc!')
  744. schema = {
  745. 'sub': {
  746. 'type': 'list',
  747. 'root_doc': True,
  748. 'schema': {
  749. 'type': 'dict',
  750. 'schema': {
  751. 'foo': {
  752. 'type': 'string',
  753. 'root_doc': True
  754. }
  755. }
  756. }
  757. }
  758. }
  759. assert_success({'sub': [{'foo': 'bar'}, {'foo': 'baz'}]},
  760. validator=MyValidator(schema))
  761. def test_validator_rule(validator):
  762. def validate_name(field, value, error):
  763. if not value.islower():
  764. error(field, 'must be lowercase')
  765. validator.schema = {
  766. 'name': {'validator': validate_name},
  767. 'age': {'type': 'integer'}
  768. }
  769. assert_fail({'name': 'ItsMe', 'age': 2}, validator=validator,
  770. error=('name', (), errors.CUSTOM, None, ('must be lowercase',)))
  771. assert validator.errors == {'name': ['must be lowercase']}
  772. assert_success({'name': 'itsme', 'age': 2}, validator=validator)
  773. def test_validated(validator):
  774. validator.schema = {'property': {'type': 'string'}}
  775. document = {'property': 'string'}
  776. assert validator.validated(document) == document
  777. document = {'property': 0}
  778. assert validator.validated(document) is None
  779. def test_anyof():
  780. # prop1 must be either a number between 0 and 10
  781. schema = {'prop1': {'min': 0, 'max': 10}}
  782. doc = {'prop1': 5}
  783. assert_success(doc, schema)
  784. # prop1 must be either a number between 0 and 10 or 100 and 110
  785. schema = {'prop1': {'anyof':
  786. [{'min': 0, 'max': 10}, {'min': 100, 'max': 110}]}}
  787. doc = {'prop1': 105}
  788. assert_success(doc, schema)
  789. # prop1 must be either a number between 0 and 10 or 100 and 110
  790. schema = {'prop1': {'anyof':
  791. [{'min': 0, 'max': 10}, {'min': 100, 'max': 110}]}}
  792. doc = {'prop1': 50}
  793. assert_fail(doc, schema)
  794. # prop1 must be an integer that is either be
  795. # greater than or equal to 0, or greater than or equal to 10
  796. schema = {'prop1': {'type': 'integer',
  797. 'anyof': [{'min': 0}, {'min': 10}]}}
  798. assert_success({'prop1': 10}, schema)
  799. # test that intermediate schemas do not sustain
  800. assert 'type' not in schema['prop1']['anyof'][0]
  801. assert 'type' not in schema['prop1']['anyof'][1]
  802. assert 'allow_unknown' not in schema['prop1']['anyof'][0]
  803. assert 'allow_unknown' not in schema['prop1']['anyof'][1]
  804. assert_success({'prop1': 5}, schema)
  805. exp_child_errors = [
  806. (('prop1',), ('prop1', 'anyof', 0, 'min'), errors.MIN_VALUE, 0),
  807. (('prop1',), ('prop1', 'anyof', 1, 'min'), errors.MIN_VALUE, 10)
  808. ]
  809. assert_fail({'prop1': -1}, schema,
  810. error=(('prop1',), ('prop1', 'anyof'), errors.ANYOF,
  811. [{'min': 0}, {'min': 10}]),
  812. child_errors=exp_child_errors)
  813. doc = {'prop1': 5.5}
  814. assert_fail(doc, schema)
  815. doc = {'prop1': '5.5'}
  816. assert_fail(doc, schema)
  817. def test_allof():
  818. # prop1 has to be a float between 0 and 10
  819. schema = {'prop1': {'allof': [
  820. {'type': 'float'}, {'min': 0}, {'max': 10}]}}
  821. doc = {'prop1': -1}
  822. assert_fail(doc, schema)
  823. doc = {'prop1': 5}
  824. assert_success(doc, schema)
  825. doc = {'prop1': 11}
  826. assert_fail(doc, schema)
  827. # prop1 has to be a float and an integer
  828. schema = {'prop1': {'allof': [{'type': 'float'}, {'type': 'integer'}]}}
  829. doc = {'prop1': 11}
  830. assert_success(doc, schema)
  831. doc = {'prop1': 11.5}
  832. assert_fail(doc, schema)
  833. doc = {'prop1': '11'}
  834. assert_fail(doc, schema)
  835. def test_unicode_allowed():
  836. # issue 280
  837. doc = {'letters': u'♄εℓł☺'}
  838. schema = {'letters': {'type': 'string', 'allowed': ['a', 'b', 'c']}}
  839. assert_fail(doc, schema)
  840. schema = {'letters': {'type': 'string', 'allowed': [u'♄εℓł☺']}}
  841. assert_success(doc, schema)
  842. schema = {'letters': {'type': 'string', 'allowed': ['♄εℓł☺']}}
  843. doc = {'letters': '♄εℓł☺'}
  844. assert_success(doc, schema)
  845. @mark.skipif(sys.version_info[0] < 3,
  846. reason='requires python 3.x')
  847. def test_unicode_allowed_py3():
  848. """ All strings are unicode in Python 3.x. Input doc and schema
  849. have equal strings and validation yield success."""
  850. # issue 280
  851. doc = {'letters': u'♄εℓł☺'}
  852. schema = {'letters': {'type': 'string', 'allowed': ['♄εℓł☺']}}
  853. assert_success(doc, schema)
  854. @mark.skipif(sys.version_info[0] > 2,
  855. reason='requires python 2.x')
  856. def test_unicode_allowed_py2():
  857. """ Python 2.x encodes value of allowed using default encoding if
  858. the string includes characters outside ASCII range. Produced string
  859. does not match input which is an unicode string."""
  860. # issue 280
  861. doc = {'letters': u'♄εℓł☺'}
  862. schema = {'letters': {'type': 'string', 'allowed': ['♄εℓł☺']}}
  863. assert_fail(doc, schema)
  864. def test_oneof():
  865. # prop1 can only only be:
  866. # - greater than 10
  867. # - greater than 0
  868. # - equal to -5, 5, or 15
  869. schema = {'prop1': {'type': 'integer', 'oneof': [
  870. {'min': 0},
  871. {'min': 10},
  872. {'allowed': [-5, 5, 15]}]}}
  873. # document is not valid
  874. # prop1 not greater than 0, 10 or equal to -5
  875. doc = {'prop1': -1}
  876. assert_fail(doc, schema)
  877. # document is valid
  878. # prop1 is less then 0, but is -5
  879. doc = {'prop1': -5}
  880. assert_success(doc, schema)
  881. # document is valid
  882. # prop1 greater than 0
  883. doc = {'prop1': 1}
  884. assert_success(doc, schema)
  885. # document is not valid
  886. # prop1 is greater than 0
  887. # and equal to 5
  888. doc = {'prop1': 5}
  889. assert_fail(doc, schema)
  890. # document is not valid
  891. # prop1 is greater than 0
  892. # and greater than 10
  893. doc = {'prop1': 11}
  894. assert_fail(doc, schema)
  895. # document is not valid
  896. # prop1 is greater than 0
  897. # and greater than 10
  898. # and equal to 15
  899. doc = {'prop1': 15}
  900. assert_fail(doc, schema)
  901. def test_noneof():
  902. # prop1 can not be:
  903. # - greater than 10
  904. # - greater than 0
  905. # - equal to -5, 5, or 15
  906. schema = {'prop1': {'type': 'integer', 'noneof': [
  907. {'min': 0},
  908. {'min': 10},
  909. {'allowed': [-5, 5, 15]}]}}
  910. # document is valid
  911. doc = {'prop1': -1}
  912. assert_success(doc, schema)
  913. # document is not valid
  914. # prop1 is equal to -5
  915. doc = {'prop1': -5}
  916. assert_fail(doc, schema)
  917. # document is not valid
  918. # prop1 greater than 0
  919. doc = {'prop1': 1}
  920. assert_fail(doc, schema)
  921. # document is not valid
  922. doc = {'prop1': 5}
  923. assert_fail(doc, schema)
  924. # document is not valid
  925. doc = {'prop1': 11}
  926. assert_fail(doc, schema)
  927. # document is not valid
  928. # and equal to 15
  929. doc = {'prop1': 15}
  930. assert_fail(doc, schema)
  931. def test_anyof_allof():
  932. # prop1 can be any number outside of [0-10]
  933. schema = {'prop1': {'allof': [{'anyof': [{'type': 'float'},
  934. {'type': 'integer'}]},
  935. {'anyof': [{'min': 10},
  936. {'max': 0}]}
  937. ]}}
  938. doc = {'prop1': 11}
  939. assert_success(doc, schema)
  940. doc = {'prop1': -1}
  941. assert_success(doc, schema)
  942. doc = {'prop1': 5}
  943. assert_fail(doc, schema)
  944. doc = {'prop1': 11.5}
  945. assert_success(doc, schema)
  946. doc = {'prop1': -1.5}
  947. assert_success(doc, schema)
  948. doc = {'prop1': 5.5}
  949. assert_fail(doc, schema)
  950. doc = {'prop1': '5.5'}
  951. assert_fail(doc, schema)
  952. def test_anyof_schema(validator):
  953. # test that a list of schemas can be specified.
  954. valid_parts =testschema': {'model number': {'type': 'string'},
  955. 'count': {'type': 'integer'}}},
  956. {'schema': {'serial number': {'type': 'string'},
  957. 'count': {'type': 'integer'}}}]
  958. valid_item = {'type': ['dict', 'string'], 'anyof': valid_parts}
  959. schema = {'parts': {'type': 'list', 'schema': valid_item}}
  960. document = {'parts': [{'model number': 'MX-009', 'count': 100},
  961. {'serial number': '898-001'},
  962. 'misc']}
  963. # document is valid. each entry in 'parts' matches a type or schema
  964. assert_success(document, schema, validator=validator)
  965. document['parts'].append({'product name': "Monitors", 'count': 18})
  966. # document is invalid. 'product name' does not match any valid schemas
  967. assert_fail(document, schema, validator=validator)
  968. document['parts'].pop()
  969. # document is valid again
  970. assert_success(document, schema, validator=validator)
  971. document['parts'].append({'product name': "Monitors", 'count': 18})
  972. document['parts'].append(10)
  973. # and invalid. numbers are not allowed.
  974. exp_child_errors = [
  975. (('parts', 3), ('parts', 'schema', 'anyof'), errors.ANYOF,
  976. valid_parts),
  977. (('parts', 4), ('parts', 'schema', 'type'), errors.BAD_TYPE,
  978. ['dict', 'string'])
  979. ]
  980. _errors = assert_fail(document, schema, validator=validator,
  981. error=('parts', ('parts', 'schema'),
  982. errors.SEQUENCE_SCHEMA, valid_item),
  983. child_errors=exp_child_errors)
  984. assert_not_has_error(_errors, ('parts', 4), ('parts', 'schema', 'anyof'),
  985. errors.ANYOF, valid_parts)
  986. # tests errors.BasicErrorHandler's tree representation
  987. v_errors = validator.errors
  988. assert 'parts' in v_errors
  989. assert 3 in v_errors['parts'][-1]
  990. assert v_errors['parts'][-1][3][0] == "no definitions validate"
  991. scope = v_errors['parts'][-1][3][-1]
  992. assert 'anyof definition 0' in scope
  993. assert 'anyof definition 1' in scope
  994. assert scope['anyof definition 0'] == [{"product name": ["unknown field"]}]
  995. assert scope['anyof definition 1'] == [{"product name": ["unknown field"]}]
  996. assert v_errors['parts'][-1][4] == ["must be of ['dict', 'string'] type"]
  997. def test_anyof_2():
  998. # these two schema should be the same
  999. schema1 = {'prop': {'anyof': [{'type': 'dict',
  1000. 'schema': {
  1001. 'val': {'type': 'integer'}}},
  1002. {'type': 'dict',
  1003. 'schema': {
  1004. 'val': {'type': 'string'}}}]}}
  1005. schema2 = {'prop': {'type': 'dict', 'anyof': [
  1006. {'schema': {'val': {'type': 'integer'}}},
  1007. {'schema': {'val': {'type': 'string'}}}]}}
  1008. doc = {'prop': {'val': 0}}
  1009. assert_success(doc, schema1)
  1010. assert_success(doc, schema2)
  1011. doc = {'prop': {'val': '0'}}
  1012. assert_success(doc, schema1)
  1013. assert_success(doc, schema2)
  1014. doc = {'prop': {'val': 1.1}}
  1015. assert_fail(doc, schema1)
  1016. assert_fail(doc, schema2)
  1017. def test_anyof_type():
  1018. schema = {'anyof_type': {'anyof_type': ['string', 'integer']}}
  1019. assert_success({'anyof_type': 'bar'}, schema)
  1020. assert_success({'anyof_type': 23}, schema)
  1021. def test_oneof_schema():
  1022. schema = {'oneof_schema': {'type': 'dict',
  1023. 'oneof_schema':
  1024. [{'digits': {'type': 'integer',
  1025. 'min': 0, 'max': 99}},
  1026. {'text': {'type': 'string',
  1027. 'regex': '^[0-9]{2}$'}}]}}
  1028. assert_success({'oneof_schema': {'digits': 19}}, schema)
  1029. assert_success({'oneof_schema': {'text': '84'}}, schema)
  1030. assert_fail({'oneof_schema': {'digits': 19, 'text': '84'}}, schema)
  1031. def test_nested_oneof_type():
  1032. schema = {'nested_oneof_type':
  1033. {'valueschema': {'oneof_type': ['string', 'integer']}}}
  1034. assert_success({'nested_oneof_type': {'foo': 'a'}}, schema)
  1035. assert_success({'nested_oneof_type': {'bar': 3}}, schema)
  1036. def test_nested_oneofs(validator):
  1037. validator.schema = {'abc': {
  1038. 'type': 'dict',
  1039. 'oneof_schema': [
  1040. {'foo': {
  1041. 'type': 'dict',
  1042. 'schema': {'bar': {'oneof_type': ['integer', 'float']}}
  1043. }},
  1044. {'baz': {'type': 'string'}}
  1045. ]}}
  1046. document = {'abc': {'foo': {'bar': 'bad'}}}
  1047. expected_errors = {
  1048. 'abc': [
  1049. 'none or more than one rule validate',
  1050. {'oneof definition 0': [
  1051. {'foo': [{'bar': [
  1052. 'none or more than one rule validate',
  1053. {'oneof definition 0': ['must be of integer type'],
  1054. 'oneof definition 1': ['must be of float type']}
  1055. ]}]}],
  1056. 'oneof definition 1': [{'foo': ['unknown field']}]}
  1057. ]
  1058. }
  1059. assert_fail(document, validator=validator)
  1060. assert validator.errors == expected_errors
  1061. def test_no_of_validation_if_type_fails(validator):
  1062. valid_parts = [{'schema': {'model number': {'type': 'string'},
  1063. 'count': {'type': 'integer'}}},
  1064. {'schema': {'serial number': {'type': 'string'},
  1065. 'count': {'type': 'integer'}}}]
  1066. validator.schema = {'part': {'type': ['dict', 'string'],
  1067. 'anyof': valid_parts}}
  1068. document = {'part': 10}
  1069. _errors = assert_fail(document, validator=validator)
  1070. assert len(_errors) == 1
  1071. def test_issue_107(validator):
  1072. schema = {'info': {'type': 'dict',
  1073. 'schema': {'name': {'type': 'string',
  1074. 'required': True}}}}
  1075. document = {'info': {'name': 'my name'}}
  1076. assert_success(document, schema, validator=validator)
  1077. v = Validator(schema)
  1078. assert_success(document, schema, v)
  1079. # it once was observed that this behaves other than the previous line
  1080. assert v.validate(document)
  1081. def test_dont_type_validate_nulled_values(validator):
  1082. assert_fail({'an_integer': None}, validator=validator)
  1083. assert validator.errors == {'an_integer': ['null value not allowed']}
  1084. def test_dependencies_error(validator):
  1085. schema = {'field1': {'required': False},
  1086. 'field2': {'required': True,
  1087. 'dependencies': {'field1': ['one', 'two']}}}
  1088. validator.validate({'field2': 7}, schema)
  1089. exp_msg = errors.BasicErrorHandler \
  1090. .messages[errors.DEPENDENCIES_FIELD_VALUE.code] \
  1091. .format(field='field2', constraint={'field1': ['one', 'two']})
  1092. assert validator.errors == {'field2': [exp_msg]}
  1093. def test_dependencies_on_boolean_field_with_one_value():
  1094. # https://github.com/pyeve/cerberus/issues/138
  1095. schema = {'deleted': {'type': 'boolean'},
  1096. 'text': {'dependencies': {'deleted': False}}}
  1097. try:
  1098. assert_success({'text': 'foo', 'deleted': False}, schema)
  1099. assert_fail({'text': 'foo', 'deleted': True}, schema)
  1100. assert_fail({'text': 'foo'}, schema)
  1101. except TypeError as e:
  1102. if str(e) == "argument of type 'bool' is not iterable":
  1103. raise AssertionError(
  1104. "Bug #138 still exists, couldn't use boolean in dependency "
  1105. "without putting it in a list.\n"
  1106. "'some_field': True vs 'some_field: [True]")
  1107. else:
  1108. raise
  1109. def test_dependencies_on_boolean_field_with_value_in_list():
  1110. # https://github.com/pyeve/cerberus/issues/138
  1111. schema = {'deleted': {'type': 'boolean'},
  1112. 'text': {'dependencies': {'deleted': [False]}}}
  1113. assert_success({'text': 'foo', 'deleted': False}, schema)
  1114. assert_fail({'text': 'foo', 'deleted': True}, schema)
  1115. assert_fail({'text': 'foo'}, schema)
  1116. def test_document_path():
  1117. class DocumentPathTester(Validator):
  1118. def _validate_trail(self, constraint, field, value):
  1119. """ {'type': 'boolean'} """
  1120. test_doc = self.root_document
  1121. for crumb in self.document_path:
  1122. test_doc = test_doc[crumb]
  1123. assert test_doc == self.document
  1124. v = DocumentPathTester()
  1125. schema = {'foo': {'schema': {'bar': {'trail': True}}}}
  1126. document = {'foo': {'bar': {}}}
  1127. assert_success(document, schema, validator=v)
  1128. def test_excludes():
  1129. schema = {'this_field': {'type': 'dict',
  1130. 'excludes': 'that_field'},
  1131. 'that_field': {'type': 'dict'}}
  1132. assert_success({'this_field': {}}, schema)
  1133. assert_success({'that_field': {}}, schema)
  1134. assert_success({}, schema)
  1135. assert_fail({'that_field': {}, 'this_field': {}}, schema)
  1136. def test_mutual_excludes():
  1137. schema = {'this_field': {'type': 'dict',
  1138. 'excludes': 'that_field'},
  1139. 'that_field': {'type': 'dict',
  1140. 'excludes': 'this_field'}}
  1141. assert_success({'this_field': {}}, schema)
  1142. assert_success({'that_field': {}}, schema)
  1143. assert_success({}, schema)
  1144. assert_fail({'that_field': {}, 'this_field': {}}, schema)
  1145. def test_required_excludes():
  1146. schema = {'this_field': {'type': 'dict',
  1147. 'excludes': 'that_field',
  1148. 'required': True},
  1149. 'that_field': {'type': 'dict',
  1150. 'excludes': 'this_field',
  1151. 'required': True}}
  1152. assert_success({'this_field': {}}, schema, update=False)
  1153. assert_success({'that_field': {}}, schema, update=False)
  1154. assert_fail({}, schema)
  1155. assert_fail({'that_field': {}, 'this_field': {}}, schema)
  1156. def test_multiples_exclusions():
  1157. schema = {'this_field': {'type': 'dict',
  1158. 'excludes': ['that_field', 'bazo_field']},
  1159. 'that_field': {'type': 'dict',
  1160. 'excludes': 'this_field'},
  1161. 'bazo_field': {'type': 'dict'}}
  1162. assert_success({'this_field': {}}, schema)
  1163. assert_success({'that_field': {}}, schema)
  1164. assert_fail({'this_field': {}, 'that_field': {}}, schema)
  1165. assert_fail({'this_field': {}, 'bazo_field': {}}, schema)
  1166. assert_fail({'that_field': {}, 'this_field': {}, 'bazo_field': {}}, schema)
  1167. assert_success({'that_field': {}, 'bazo_field': {}}, schema)
  1168. def test_bad_excludes_fields(validator):
  1169. validator.schema = {'this_field': {'type': 'dict',
  1170. 'excludes': ['that_field', 'bazo_field'],
  1171. 'required': True},
  1172. 'that_field': {'type': 'dict',
  1173. 'excludes': 'this_field',
  1174. 'required': True}}
  1175. assert_fail({'that_field': {}, 'this_field': {}}, validator=validator)
  1176. handler = errors.BasicErrorHandler
  1177. assert (validator.errors ==
  1178. {'that_field':
  1179. [handler.messages[errors.EXCLUDES_FIELD.code].format(
  1180. "'this_field'", field="that_field")],
  1181. 'this_field':
  1182. [handler.messages[errors.EXCLUDES_FIELD.code].format(
  1183. "'that_field', 'bazo_field'", field="this_field")]})
  1184. def test_boolean_is_not_a_number():
  1185. # https://github.com/pyeve/cerberus/issues/144
  1186. assert_fail({'value': True}, {'value': {'type': 'number'}})
  1187. def test_min_max_date():
  1188. schema = {'date': {'min': date(1900, 1, 1), 'max': date(1999, 12, 31)}}
  1189. assert_success({'date': date(1945, 5, 8)}, schema)
  1190. assert_fail({'date': date(1871, 5, 10)}, schema)
  1191. def test_dict_length():
  1192. schema = {'dict': {'minlength': 1}}
  1193. assert_fail({'dict': {}}, schema)
  1194. assert_success({'dict': {'foo': 'bar'}}, schema)
  1195. def test_forbidden():
  1196. schema = {'user': {'forbidden': ['root', 'admin']}}
  1197. assert_fail({'user': 'admin'}, schema)
  1198. assert_success({'user': 'alice'}, schema)
  1199. def test_mapping_with_sequence_schema():
  1200. schema = {'list': {'schema': {'allowed': ['a', 'b', 'c']}}}
  1201. document = {'list': {'is_a': 'mapping'}}
  1202. assert_fail(document, schema,
  1203. error=('list', ('list', 'schema'), errors.BAD_TYPE_FOR_SCHEMA,
  1204. schema['list']['schema']))
  1205. def test_sequence_with_mapping_schema():
  1206. schema = {'list': {'schema': {'foo': {'allowed': ['a', 'b', 'c']}},
  1207. 'type': 'dict'}}
  1208. document = {'list': ['a', 'b', 'c']}
  1209. assert_fail(document, schema)
  1210. def test_type_error_aborts_validation():
  1211. schema = {'foo': {'type': 'string', 'allowed': ['a']}}
  1212. document = {'foo': 0}
  1213. assert_fail(document, schema,
  1214. error=('foo', ('foo', 'type'), errors.BAD_TYPE, 'string'))
  1215. def test_dependencies_in_oneof():
  1216. # https://github.com/pyeve/cerberus/issues/241
  1217. schema = {'a': {'type': 'integer',
  1218. 'oneof': [
  1219. {'allowed': [1], 'dependencies': 'b'},
  1220. {'allowed': [2], 'dependencies': 'c'}
  1221. ]},
  1222. 'b': {},
  1223. 'c': {}}
  1224. assert_success({'a': 1, 'b': 'foo'}, schema)
  1225. assert_success({'a': 2, 'c': 'bar'}, schema)
  1226. assert_fail({'a': 1, 'c': 'foo'}, schema)
  1227. assert_fail({'a': 2, 'b': 'bar'}, schema)
  1228. def test_allow_unknown_with_oneof_rules(validator):
  1229. # https://github.com/pyeve/cerberus/issues/251
  1230. schema = {
  1231. 'test': {
  1232. 'oneof': [
  1233. {
  1234. test 'type': 'dict',
  1235. 'allow_unknown': True,
  1236. 'schema': {'known': {'type': 'string'}}
  1237. },
  1238. {
  1239. 'type': 'dict',
  1240. 'schema': {'known': {'type': 'string'}}
  1241. },
  1242. ]
  1243. }
  1244. }
  1245. # check regression and that allow unknown does not cause any different
  1246. # than expected behaviour for one-of.
  1247. document = {'test': {'known': 's'}}
  1248. validator(document, schema)
  1249. _errotest validator._errors
  1250. assert len(_errors) == 1
  1251. assert_has_error(_errors, 'test', ('test', 'oneof'),
  1252. errors.ONEOF, schtest'testtestoneof'])
  1253. assert len(_errors[0].child_errors) == 0
  1254. testcheck that allow_unknown is actually applied
  1255. document = {'test': {'known': 's', 'unknown': 'asd'}}
  1256. assert_success(docutest, validator=validator)