test_schema.py 94 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347
  1. # -*- coding: utf-8 -*-
  2. # pylint: disable=function-redefined,missing-docstring
  3. # TODO: Remove the following PyLint disable as soon as we support YAML and RST rendering
  4. # pylint: disable=abstract-method
  5. # Import python libs
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. import copy
  8. # Import Salt Testing Libs
  9. from tests.support.unit import TestCase, skipIf
  10. # Import Salt Libs
  11. import salt.utils.json
  12. import salt.utils.stringutils
  13. import salt.utils.yaml
  14. import salt.utils.schema as schema
  15. from salt.ext import six
  16. from salt.utils.versions import LooseVersion as _LooseVersion
  17. # Import 3rd-party libs
  18. try:
  19. import jsonschema
  20. import jsonschema.exceptions
  21. HAS_JSONSCHEMA = True
  22. JSONSCHEMA_VERSION = _LooseVersion(jsonschema.__version__)
  23. except ImportError:
  24. HAS_JSONSCHEMA = False
  25. JSONSCHEMA_VERSION = _LooseVersion('0')
  26. # pylint: disable=unused-import
  27. try:
  28. import rfc3987
  29. HAS_RFC3987 = True
  30. except ImportError:
  31. HAS_RFC3987 = False
  32. try:
  33. import strict_rfc3339
  34. HAS_STRICT_RFC3339 = True
  35. except ImportError:
  36. HAS_STRICT_RFC3339 = False
  37. try:
  38. import isodate
  39. HAS_ISODATE = True
  40. except ImportError:
  41. HAS_ISODATE = False
  42. # pylint: enable=unused-import
  43. class ConfigTestCase(TestCase):
  44. '''
  45. TestCase for salt.utils.config module
  46. '''
  47. def test_configuration_subclass_inherits_items(self):
  48. class BaseConfig(schema.Schema):
  49. base = schema.BooleanItem(default=True, required=True)
  50. class SubClassedConfig(BaseConfig):
  51. hungry = schema.BooleanItem(title='Hungry', description='Are you hungry?', required=True)
  52. self.assertDictEqual(
  53. SubClassedConfig.serialize(),
  54. {
  55. '$schema': 'http://json-schema.org/draft-04/schema#',
  56. 'type': 'object',
  57. 'properties': {
  58. 'base': {
  59. 'default': True,
  60. 'type': 'boolean',
  61. 'title': 'base'
  62. },
  63. 'hungry': {
  64. 'type': 'boolean',
  65. 'description': 'Are you hungry?',
  66. 'title': 'Hungry'
  67. }
  68. },
  69. 'required': ['base', 'hungry'],
  70. 'x-ordering': ['base', 'hungry'],
  71. 'additionalProperties': False,
  72. }
  73. )
  74. class MergedConfigClass(schema.Schema):
  75. thirsty = schema.BooleanItem(title='Thirsty', description='Are you thirsty?', required=True)
  76. merge_subclassed = SubClassedConfig(flatten=True)
  77. expected = {
  78. '$schema': 'http://json-schema.org/draft-04/schema#',
  79. 'type': 'object',
  80. 'properties': {
  81. 'thirsty': {
  82. 'type': 'boolean',
  83. 'description': 'Are you thirsty?',
  84. 'title': 'Thirsty'
  85. },
  86. 'base': {
  87. 'default': True,
  88. 'type': 'boolean',
  89. 'title': 'base'
  90. },
  91. 'hungry': {
  92. 'type': 'boolean',
  93. 'description': 'Are you hungry?',
  94. 'title': 'Hungry'
  95. }
  96. },
  97. 'required': ['thirsty', 'base', 'hungry'],
  98. 'x-ordering': ['thirsty', 'base', 'hungry'],
  99. 'additionalProperties': False,
  100. }
  101. self.assertDictContainsSubset(
  102. MergedConfigClass.serialize()['properties'],
  103. expected['properties']
  104. )
  105. self.assertDictContainsSubset(
  106. expected,
  107. MergedConfigClass.serialize()
  108. )
  109. def test_configuration_items_order(self):
  110. class One(schema.Schema):
  111. one = schema.BooleanItem()
  112. class Three(schema.Schema):
  113. three = schema.BooleanItem()
  114. class Final(One):
  115. two = schema.BooleanItem()
  116. three = Three(flatten=True)
  117. self.assertEqual(Final.serialize()['x-ordering'], ['one', 'two', 'three'])
  118. def test_optional_requirements_config(self):
  119. class BaseRequirements(schema.Schema):
  120. driver = schema.StringItem(default='digitalocean', format='hidden')
  121. class SSHKeyFileSchema(schema.Schema):
  122. ssh_key_file = schema.StringItem(
  123. title='SSH Private Key',
  124. description='The path to an SSH private key which will be used '
  125. 'to authenticate on the deployed VMs',
  126. )
  127. class SSHKeyNamesSchema(schema.Schema):
  128. ssh_key_names = schema.StringItem(
  129. title='SSH Key Names',
  130. description='The names of an SSH key being managed on '
  131. 'DigitalOcean account which will be used to '
  132. 'authenticate on the deployed VMs',
  133. )
  134. class Requirements(BaseRequirements):
  135. title = 'DigitalOcean'
  136. description = 'DigitalOcean Cloud VM configuration requirements.'
  137. personal_access_token = schema.StringItem(
  138. title='Personal Access Token',
  139. description='This is the API access token which can be generated '
  140. 'under the API/Application on your account',
  141. required=True)
  142. requirements_definition = schema.AnyOfItem(
  143. items=(
  144. SSHKeyFileSchema.as_requirements_item(),
  145. SSHKeyNamesSchema.as_requirements_item()
  146. ),
  147. )(flatten=True)
  148. ssh_key_file = SSHKeyFileSchema(flatten=True)
  149. ssh_key_names = SSHKeyNamesSchema(flatten=True)
  150. expected = {
  151. '$schema': 'http://json-schema.org/draft-04/schema#',
  152. 'title': 'DigitalOcean',
  153. 'description': 'DigitalOcean Cloud VM configuration requirements.',
  154. 'type': 'object',
  155. 'properties': {
  156. 'driver': {
  157. 'default': 'digitalocean',
  158. 'format': 'hidden',
  159. 'type': 'string',
  160. 'title': 'driver'
  161. },
  162. 'personal_access_token': {
  163. 'type': 'string',
  164. 'description': 'This is the API access token which can be '
  165. 'generated under the API/Application on your account',
  166. 'title': 'Personal Access Token'
  167. },
  168. 'ssh_key_file': {
  169. 'type': 'string',
  170. 'description': 'The path to an SSH private key which will '
  171. 'be used to authenticate on the deployed VMs',
  172. 'title': 'SSH Private Key'
  173. },
  174. 'ssh_key_names': {
  175. 'type': 'string',
  176. 'description': 'The names of an SSH key being managed on DigitalOcean '
  177. 'account which will be used to authenticate on the deployed VMs',
  178. 'title': 'SSH Key Names'
  179. }
  180. },
  181. 'anyOf': [
  182. {'required': ['ssh_key_file']},
  183. {'required': ['ssh_key_names']}
  184. ],
  185. 'required': [
  186. 'personal_access_token'
  187. ],
  188. 'x-ordering': [
  189. 'driver',
  190. 'personal_access_token',
  191. 'ssh_key_file',
  192. 'ssh_key_names',
  193. ],
  194. 'additionalProperties': False
  195. }
  196. self.assertDictEqual(expected, Requirements.serialize())
  197. class Requirements2(BaseRequirements):
  198. title = 'DigitalOcean'
  199. description = 'DigitalOcean Cloud VM configuration requirements.'
  200. personal_access_token = schema.StringItem(
  201. title='Personal Access Token',
  202. description='This is the API access token which can be generated '
  203. 'under the API/Application on your account',
  204. required=True)
  205. ssh_key_file = schema.StringItem(
  206. title='SSH Private Key',
  207. description='The path to an SSH private key which will be used '
  208. 'to authenticate on the deployed VMs')
  209. ssh_key_names = schema.StringItem(
  210. title='SSH Key Names',
  211. description='The names of an SSH key being managed on '
  212. 'DigitalOcean account which will be used to '
  213. 'authenticate on the deployed VMs')
  214. requirements_definition = schema.AnyOfItem(
  215. items=(
  216. schema.RequirementsItem(requirements=['ssh_key_file']),
  217. schema.RequirementsItem(requirements=['ssh_key_names'])
  218. ),
  219. )(flatten=True)
  220. expected = {
  221. '$schema': 'http://json-schema.org/draft-04/schema#',
  222. 'title': 'DigitalOcean',
  223. 'description': 'DigitalOcean Cloud VM configuration requirements.',
  224. 'type': 'object',
  225. 'properties': {
  226. 'driver': {
  227. 'default': 'digitalocean',
  228. 'format': 'hidden',
  229. 'type': 'string',
  230. 'title': 'driver'
  231. },
  232. 'personal_access_token': {
  233. 'type': 'string',
  234. 'description': 'This is the API access token which can be '
  235. 'generated under the API/Application on your account',
  236. 'title': 'Personal Access Token'
  237. },
  238. 'ssh_key_file': {
  239. 'type': 'string',
  240. 'description': 'The path to an SSH private key which will '
  241. 'be used to authenticate on the deployed VMs',
  242. 'title': 'SSH Private Key'
  243. },
  244. 'ssh_key_names': {
  245. 'type': 'string',
  246. 'description': 'The names of an SSH key being managed on DigitalOcean '
  247. 'account which will be used to authenticate on the deployed VMs',
  248. 'title': 'SSH Key Names'
  249. }
  250. },
  251. 'anyOf': [
  252. {'required': ['ssh_key_file']},
  253. {'required': ['ssh_key_names']}
  254. ],
  255. 'required': [
  256. 'personal_access_token'
  257. ],
  258. 'x-ordering': [
  259. 'driver',
  260. 'personal_access_token',
  261. 'ssh_key_file',
  262. 'ssh_key_names',
  263. ],
  264. 'additionalProperties': False
  265. }
  266. self.assertDictContainsSubset(expected, Requirements2.serialize())
  267. class Requirements3(schema.Schema):
  268. title = 'DigitalOcean'
  269. description = 'DigitalOcean Cloud VM configuration requirements.'
  270. merge_reqs = Requirements(flatten=True)
  271. expected = {
  272. '$schema': 'http://json-schema.org/draft-04/schema#',
  273. 'title': 'DigitalOcean',
  274. 'description': 'DigitalOcean Cloud VM configuration requirements.',
  275. 'type': 'object',
  276. 'properties': {
  277. 'driver': {
  278. 'default': 'digitalocean',
  279. 'format': 'hidden',
  280. 'type': 'string',
  281. 'title': 'driver'
  282. },
  283. 'personal_access_token': {
  284. 'type': 'string',
  285. 'description': 'This is the API access token which can be '
  286. 'generated under the API/Application on your account',
  287. 'title': 'Personal Access Token'
  288. },
  289. 'ssh_key_file': {
  290. 'type': 'string',
  291. 'description': 'The path to an SSH private key which will '
  292. 'be used to authenticate on the deployed VMs',
  293. 'title': 'SSH Private Key'
  294. },
  295. 'ssh_key_names': {
  296. 'type': 'string',
  297. 'description': 'The names of an SSH key being managed on DigitalOcean '
  298. 'account which will be used to authenticate on the deployed VMs',
  299. 'title': 'SSH Key Names'
  300. }
  301. },
  302. 'anyOf': [
  303. {'required': ['ssh_key_file']},
  304. {'required': ['ssh_key_names']}
  305. ],
  306. 'required': [
  307. 'personal_access_token'
  308. ],
  309. 'x-ordering': [
  310. 'driver',
  311. 'personal_access_token',
  312. 'ssh_key_file',
  313. 'ssh_key_names',
  314. ],
  315. 'additionalProperties': False
  316. }
  317. self.assertDictContainsSubset(expected, Requirements3.serialize())
  318. class Requirements4(schema.Schema):
  319. title = 'DigitalOcean'
  320. description = 'DigitalOcean Cloud VM configuration requirements.'
  321. merge_reqs = Requirements(flatten=True)
  322. ssh_key_file_2 = schema.StringItem(
  323. title='SSH Private Key',
  324. description='The path to an SSH private key which will be used '
  325. 'to authenticate on the deployed VMs')
  326. ssh_key_names_2 = schema.StringItem(
  327. title='SSH Key Names',
  328. description='The names of an SSH key being managed on '
  329. 'DigitalOcean account which will be used to '
  330. 'authenticate on the deployed VMs')
  331. requirements_definition_2 = schema.AnyOfItem(
  332. items=(
  333. schema.RequirementsItem(requirements=['ssh_key_file_2']),
  334. schema.RequirementsItem(requirements=['ssh_key_names_2'])
  335. ),
  336. )(flatten=True)
  337. expected = {
  338. '$schema': 'http://json-schema.org/draft-04/schema#',
  339. 'title': 'DigitalOcean',
  340. 'description': 'DigitalOcean Cloud VM configuration requirements.',
  341. 'type': 'object',
  342. 'properties': {
  343. 'driver': {
  344. 'default': 'digitalocean',
  345. 'format': 'hidden',
  346. 'type': 'string',
  347. 'title': 'driver'
  348. },
  349. 'personal_access_token': {
  350. 'type': 'string',
  351. 'description': 'This is the API access token which can be '
  352. 'generated under the API/Application on your account',
  353. 'title': 'Personal Access Token'
  354. },
  355. 'ssh_key_file': {
  356. 'type': 'string',
  357. 'description': 'The path to an SSH private key which will '
  358. 'be used to authenticate on the deployed VMs',
  359. 'title': 'SSH Private Key'
  360. },
  361. 'ssh_key_names': {
  362. 'type': 'string',
  363. 'description': 'The names of an SSH key being managed on DigitalOcean '
  364. 'account which will be used to authenticate on the deployed VMs',
  365. 'title': 'SSH Key Names'
  366. },
  367. 'ssh_key_file_2': {
  368. 'type': 'string',
  369. 'description': 'The path to an SSH private key which will '
  370. 'be used to authenticate on the deployed VMs',
  371. 'title': 'SSH Private Key'
  372. },
  373. 'ssh_key_names_2': {
  374. 'type': 'string',
  375. 'description': 'The names of an SSH key being managed on DigitalOcean '
  376. 'account which will be used to authenticate on the deployed VMs',
  377. 'title': 'SSH Key Names'
  378. }
  379. },
  380. 'anyOf': [
  381. {'required': ['ssh_key_file']},
  382. {'required': ['ssh_key_names']},
  383. {'required': ['ssh_key_file_2']},
  384. {'required': ['ssh_key_names_2']}
  385. ],
  386. 'required': [
  387. 'personal_access_token'
  388. ],
  389. 'x-ordering': [
  390. 'driver',
  391. 'personal_access_token',
  392. 'ssh_key_file',
  393. 'ssh_key_names',
  394. 'ssh_key_file_2',
  395. 'ssh_key_names_2',
  396. ],
  397. 'additionalProperties': False
  398. }
  399. self.assertDictContainsSubset(expected, Requirements4.serialize())
  400. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  401. def test_optional_requirements_config_validation(self):
  402. class BaseRequirements(schema.Schema):
  403. driver = schema.StringItem(default='digitalocean', format='hidden')
  404. class SSHKeyFileSchema(schema.Schema):
  405. ssh_key_file = schema.StringItem(
  406. title='SSH Private Key',
  407. description='The path to an SSH private key which will be used '
  408. 'to authenticate on the deployed VMs')
  409. class SSHKeyNamesSchema(schema.Schema):
  410. ssh_key_names = schema.StringItem(
  411. title='SSH Key Names',
  412. description='The names of an SSH key being managed on '
  413. 'Digial Ocean account which will be used to '
  414. 'authenticate on the deployed VMs')
  415. class Requirements(BaseRequirements):
  416. title = 'DigitalOcean'
  417. description = 'DigitalOcean Cloud VM configuration requirements.'
  418. personal_access_token = schema.StringItem(
  419. title='Personal Access Token',
  420. description='This is the API access token which can be generated '
  421. 'under the API/Application on your account',
  422. required=True)
  423. requirements_definition = schema.AnyOfItem(
  424. items=(
  425. SSHKeyFileSchema.as_requirements_item(),
  426. SSHKeyNamesSchema.as_requirements_item()
  427. ),
  428. )(flatten=True)
  429. ssh_key_file = SSHKeyFileSchema(flatten=True)
  430. ssh_key_names = SSHKeyNamesSchema(flatten=True)
  431. try:
  432. jsonschema.validate(
  433. {'personal_access_token': 'foo', 'ssh_key_names': 'bar', 'ssh_key_file': 'test'},
  434. Requirements.serialize()
  435. )
  436. except jsonschema.exceptions.ValidationError as exc:
  437. self.fail('ValidationError raised: {0}'.format(exc))
  438. try:
  439. jsonschema.validate(
  440. {'personal_access_token': 'foo', 'ssh_key_names': 'bar'},
  441. Requirements.serialize()
  442. )
  443. except jsonschema.exceptions.ValidationError as exc:
  444. self.fail('ValidationError raised: {0}'.format(exc))
  445. try:
  446. jsonschema.validate(
  447. {'personal_access_token': 'foo', 'ssh_key_file': 'test'},
  448. Requirements.serialize()
  449. )
  450. except jsonschema.exceptions.ValidationError as exc:
  451. self.fail('ValidationError raised: {0}'.format(exc))
  452. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  453. jsonschema.validate(
  454. {'personal_access_token': 'foo'},
  455. Requirements.serialize()
  456. )
  457. if JSONSCHEMA_VERSION >= _LooseVersion('3.0.0'):
  458. self.assertIn('\'ssh_key_file\' is a required property', excinfo.exception.message)
  459. else:
  460. self.assertIn('is not valid under any of the given schemas', excinfo.exception.message)
  461. def test_boolean_config(self):
  462. item = schema.BooleanItem(title='Hungry', description='Are you hungry?')
  463. self.assertDictEqual(
  464. item.serialize(), {
  465. 'type': 'boolean',
  466. 'title': item.title,
  467. 'description': item.description
  468. }
  469. )
  470. item = schema.BooleanItem(title='Hungry',
  471. description='Are you hungry?',
  472. default=False)
  473. self.assertDictEqual(
  474. item.serialize(), {
  475. 'type': 'boolean',
  476. 'title': item.title,
  477. 'description': item.description,
  478. 'default': item.default
  479. }
  480. )
  481. item = schema.BooleanItem(title='Hungry',
  482. description='Are you hungry?',
  483. default=schema.Null)
  484. self.assertDictEqual(
  485. item.serialize(), {
  486. 'type': 'boolean',
  487. 'title': item.title,
  488. 'description': item.description,
  489. 'default': None
  490. }
  491. )
  492. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  493. def test_boolean_config_validation(self):
  494. class TestConf(schema.Schema):
  495. item = schema.BooleanItem(title='Hungry', description='Are you hungry?')
  496. try:
  497. jsonschema.validate({'item': False}, TestConf.serialize())
  498. except jsonschema.exceptions.ValidationError as exc:
  499. self.fail('ValidationError raised: {0}'.format(exc))
  500. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  501. jsonschema.validate({'item': 1}, TestConf.serialize())
  502. self.assertIn('is not of type', excinfo.exception.message)
  503. def test_string_config(self):
  504. item = schema.StringItem(title='Foo', description='Foo Item')
  505. self.assertDictEqual(
  506. item.serialize(), {
  507. 'type': 'string',
  508. 'title': item.title,
  509. 'description': item.description
  510. }
  511. )
  512. item = schema.StringItem(title='Foo', description='Foo Item', min_length=1, max_length=3)
  513. self.assertDictEqual(
  514. item.serialize(), {
  515. 'type': 'string',
  516. 'title': item.title,
  517. 'description': item.description,
  518. 'minLength': item.min_length,
  519. 'maxLength': item.max_length
  520. }
  521. )
  522. item = schema.StringItem(title='Foo',
  523. description='Foo Item',
  524. min_length=1,
  525. max_length=3,
  526. default='foo')
  527. self.assertDictEqual(
  528. item.serialize(), {
  529. 'type': 'string',
  530. 'title': item.title,
  531. 'description': item.description,
  532. 'minLength': item.min_length,
  533. 'maxLength': item.max_length,
  534. 'default': 'foo'
  535. }
  536. )
  537. item = schema.StringItem(title='Foo',
  538. description='Foo Item',
  539. min_length=1,
  540. max_length=3,
  541. enum=('foo', 'bar'))
  542. self.assertDictEqual(
  543. item.serialize(), {
  544. 'type': 'string',
  545. 'title': item.title,
  546. 'description': item.description,
  547. 'minLength': item.min_length,
  548. 'maxLength': item.max_length,
  549. 'enum': ['foo', 'bar']
  550. }
  551. )
  552. item = schema.StringItem(title='Foo',
  553. description='Foo Item',
  554. min_length=1,
  555. max_length=3,
  556. enum=('foo', 'bar'),
  557. enumNames=('Foo', 'Bar'))
  558. self.assertDictEqual(
  559. item.serialize(), {
  560. 'type': 'string',
  561. 'title': item.title,
  562. 'description': item.description,
  563. 'minLength': item.min_length,
  564. 'maxLength': item.max_length,
  565. 'enum': ['foo', 'bar'],
  566. 'enumNames': ['Foo', 'Bar']
  567. }
  568. )
  569. item = schema.StringItem(title='Foo',
  570. description='Foo Item',
  571. pattern=r'^([\w_-]+)$')
  572. self.assertDictEqual(
  573. item.serialize(), {
  574. 'type': 'string',
  575. 'title': item.title,
  576. 'description': item.description,
  577. 'pattern': item.pattern
  578. }
  579. )
  580. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  581. def test_string_config_validation(self):
  582. class TestConf(schema.Schema):
  583. item = schema.StringItem(title='Foo', description='Foo Item')
  584. try:
  585. jsonschema.validate({'item': 'the item'}, TestConf.serialize())
  586. except jsonschema.exceptions.ValidationError as exc:
  587. self.fail('ValidationError raised: {0}'.format(exc))
  588. class TestConf(schema.Schema):
  589. item = schema.StringItem(title='Foo', description='Foo Item',
  590. min_length=1, max_length=10)
  591. try:
  592. jsonschema.validate({'item': 'the item'}, TestConf.serialize())
  593. except jsonschema.exceptions.ValidationError as exc:
  594. self.fail('ValidationError raised: {0}'.format(exc))
  595. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  596. jsonschema.validate({'item': 3}, TestConf.serialize())
  597. self.assertIn('is not of type', excinfo.exception.message)
  598. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  599. jsonschema.validate({'item': 'the item the item'}, TestConf.serialize())
  600. self.assertIn('is too long', excinfo.exception.message)
  601. class TestConf(schema.Schema):
  602. item = schema.StringItem(title='Foo', description='Foo Item',
  603. min_length=10, max_length=100)
  604. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  605. jsonschema.validate({'item': 'the item'}, TestConf.serialize())
  606. self.assertIn('is too short', excinfo.exception.message)
  607. class TestConf(schema.Schema):
  608. item = schema.StringItem(title='Foo',
  609. description='Foo Item',
  610. enum=('foo', 'bar'))
  611. try:
  612. jsonschema.validate({'item': 'foo'}, TestConf.serialize())
  613. except jsonschema.exceptions.ValidationError as exc:
  614. self.fail('ValidationError raised: {0}'.format(exc))
  615. class TestConf(schema.Schema):
  616. item = schema.StringItem(title='Foo',
  617. description='Foo Item',
  618. enum=('foo', 'bar'))
  619. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  620. jsonschema.validate({'item': 'bin'}, TestConf.serialize())
  621. self.assertIn('is not one of', excinfo.exception.message)
  622. class TestConf(schema.Schema):
  623. item = schema.StringItem(title='Foo', description='Foo Item',
  624. pattern=r'^([\w_-]+)$')
  625. try:
  626. jsonschema.validate({'item': 'the-item'}, TestConf.serialize(),
  627. format_checker=jsonschema.FormatChecker())
  628. except jsonschema.exceptions.ValidationError as exc:
  629. self.fail('ValidationError raised: {0}'.format(exc))
  630. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  631. jsonschema.validate({'item': 'the item'}, TestConf.serialize(),
  632. format_checker=jsonschema.FormatChecker())
  633. self.assertIn('does not match', excinfo.exception.message)
  634. def test_email_config(self):
  635. item = schema.EMailItem(title='Foo', description='Foo Item')
  636. self.assertDictEqual(
  637. item.serialize(), {
  638. 'type': 'string',
  639. 'title': item.title,
  640. 'description': item.description,
  641. 'format': item.format
  642. }
  643. )
  644. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  645. def test_email_config_validation(self):
  646. class TestConf(schema.Schema):
  647. item = schema.EMailItem(title='Item', description='Item description')
  648. try:
  649. jsonschema.validate({'item': 'nobody@nowhere.com'}, TestConf.serialize(),
  650. format_checker=jsonschema.FormatChecker())
  651. except jsonschema.exceptions.ValidationError as exc:
  652. self.fail('ValidationError raised: {0}'.format(exc))
  653. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  654. jsonschema.validate({'item': '3'}, TestConf.serialize(),
  655. format_checker=jsonschema.FormatChecker())
  656. self.assertIn('is not a', excinfo.exception.message)
  657. def test_ipv4_config(self):
  658. item = schema.IPv4Item(title='Foo', description='Foo Item')
  659. self.assertDictEqual(
  660. item.serialize(), {
  661. 'type': 'string',
  662. 'title': item.title,
  663. 'description': item.description,
  664. 'format': item.format
  665. }
  666. )
  667. @skipIf(JSONSCHEMA_VERSION <= _LooseVersion('2.5.0'), 'Requires jsonschema 2.5.0 or greater')
  668. def test_ipv4_config_validation(self):
  669. class TestConf(schema.Schema):
  670. item = schema.IPv4Item(title='Item', description='Item description')
  671. try:
  672. jsonschema.validate({'item': '127.0.0.1'}, TestConf.serialize(),
  673. format_checker=jsonschema.FormatChecker())
  674. except jsonschema.exceptions.ValidationError as exc:
  675. self.fail('ValidationError raised: {0}'.format(exc))
  676. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  677. jsonschema.validate({'item': '3'}, TestConf.serialize(),
  678. format_checker=jsonschema.FormatChecker())
  679. self.assertIn('is not a', excinfo.exception.message)
  680. def test_ipv6_config(self):
  681. item = schema.IPv6Item(title='Foo', description='Foo Item')
  682. self.assertDictEqual(
  683. item.serialize(), {
  684. 'type': 'string',
  685. 'title': item.title,
  686. 'description': item.description,
  687. 'format': item.format
  688. }
  689. )
  690. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  691. def test_ipv6_config_validation(self):
  692. class TestConf(schema.Schema):
  693. item = schema.IPv6Item(title='Item', description='Item description')
  694. try:
  695. jsonschema.validate(
  696. {'item': salt.utils.stringutils.to_str('::1')},
  697. TestConf.serialize(),
  698. format_checker=jsonschema.FormatChecker())
  699. except jsonschema.exceptions.ValidationError as exc:
  700. self.fail('ValidationError raised: {0}'.format(exc))
  701. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  702. jsonschema.validate({'item': '3'}, TestConf.serialize(),
  703. format_checker=jsonschema.FormatChecker())
  704. self.assertIn('is not a', excinfo.exception.message)
  705. def test_hostname_config(self):
  706. item = schema.HostnameItem(title='Foo', description='Foo Item')
  707. self.assertDictEqual(
  708. item.serialize(), {
  709. 'type': 'string',
  710. 'title': item.title,
  711. 'description': item.description,
  712. 'format': item.format
  713. }
  714. )
  715. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  716. def test_hostname_config_validation(self):
  717. class TestConf(schema.Schema):
  718. item = schema.HostnameItem(title='Item', description='Item description')
  719. try:
  720. jsonschema.validate({'item': 'localhost'}, TestConf.serialize(),
  721. format_checker=jsonschema.FormatChecker())
  722. except jsonschema.exceptions.ValidationError as exc:
  723. self.fail('ValidationError raised: {0}'.format(exc))
  724. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  725. jsonschema.validate({'item': '3'}, TestConf.serialize(),
  726. format_checker=jsonschema.FormatChecker())
  727. self.assertIn('is not a', excinfo.exception.message)
  728. def test_datetime_config(self):
  729. item = schema.DateTimeItem(title='Foo', description='Foo Item')
  730. self.assertDictEqual(
  731. item.serialize(), {
  732. 'type': 'string',
  733. 'title': item.title,
  734. 'description': item.description,
  735. 'format': item.format
  736. }
  737. )
  738. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  739. @skipIf(any([HAS_ISODATE, HAS_STRICT_RFC3339]) is False, 'The \'strict_rfc3339\' or \'isodate\' library is missing')
  740. def test_datetime_config_validation(self):
  741. class TestConf(schema.Schema):
  742. item = schema.DateTimeItem(title='Item', description='Item description')
  743. try:
  744. jsonschema.validate({'item': '2015-07-01T18:05:27+01:00'}, TestConf.serialize(),
  745. format_checker=jsonschema.FormatChecker())
  746. except jsonschema.exceptions.ValidationError as exc:
  747. self.fail('ValidationError raised: {0}'.format(exc))
  748. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  749. jsonschema.validate({'item': '3'}, TestConf.serialize(),
  750. format_checker=jsonschema.FormatChecker())
  751. self.assertIn('is not a', excinfo.exception.message)
  752. def test_secret_config(self):
  753. item = schema.SecretItem(title='Foo', description='Foo Item')
  754. self.assertDictEqual(
  755. item.serialize(), {
  756. 'type': 'string',
  757. 'title': item.title,
  758. 'description': item.description,
  759. 'format': item.format
  760. }
  761. )
  762. def test_uri_config(self):
  763. item = schema.UriItem(title='Foo', description='Foo Item')
  764. self.assertDictEqual(
  765. item.serialize(), {
  766. 'type': 'string',
  767. 'title': item.title,
  768. 'description': item.description,
  769. 'format': item.format
  770. }
  771. )
  772. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  773. @skipIf(HAS_RFC3987 is False, 'The \'rfc3987\' library is missing')
  774. def test_uri_config_validation(self):
  775. class TestConf(schema.Schema):
  776. item = schema.UriItem(title='Item', description='Item description')
  777. try:
  778. jsonschema.validate({'item': 'ssh://localhost'}, TestConf.serialize(),
  779. format_checker=jsonschema.FormatChecker())
  780. except jsonschema.exceptions.ValidationError as exc:
  781. self.fail('ValidationError raised: {0}'.format(exc))
  782. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  783. jsonschema.validate({'item': '3'}, TestConf.serialize(),
  784. format_checker=jsonschema.FormatChecker())
  785. self.assertIn('is not a', excinfo.exception.message)
  786. def test_number_config(self):
  787. item = schema.NumberItem(title='How many dogs', description='Question')
  788. self.assertDictEqual(
  789. item.serialize(), {
  790. 'type': 'number',
  791. 'title': item.title,
  792. 'description': item.description
  793. }
  794. )
  795. item = schema.NumberItem(title='How many dogs',
  796. description='Question',
  797. minimum=0,
  798. maximum=10)
  799. self.assertDictEqual(
  800. item.serialize(), {
  801. 'type': 'number',
  802. 'title': item.title,
  803. 'description': item.description,
  804. 'minimum': item.minimum,
  805. 'maximum': item.maximum
  806. }
  807. )
  808. item = schema.NumberItem(title='How many dogs',
  809. description='Question',
  810. multiple_of=2)
  811. self.assertDictEqual(
  812. item.serialize(), {
  813. 'type': 'number',
  814. 'title': item.title,
  815. 'description': item.description,
  816. 'multipleOf': item.multiple_of
  817. }
  818. )
  819. item = schema.NumberItem(title='How many dogs',
  820. description='Question',
  821. minimum=0,
  822. exclusive_minimum=True,
  823. maximum=10,
  824. exclusive_maximum=True)
  825. self.assertDictEqual(
  826. item.serialize(), {
  827. 'type': 'number',
  828. 'title': item.title,
  829. 'description': item.description,
  830. 'minimum': item.minimum,
  831. 'maximum': item.maximum,
  832. 'exclusiveMinimum': True,
  833. 'exclusiveMaximum': True
  834. }
  835. )
  836. item = schema.NumberItem(title='How many dogs',
  837. description='Question',
  838. minimum=0,
  839. maximum=10,
  840. default=0)
  841. self.assertDictEqual(
  842. item.serialize(), {
  843. 'type': 'number',
  844. 'title': item.title,
  845. 'description': item.description,
  846. 'minimum': item.minimum,
  847. 'maximum': item.maximum,
  848. 'default': 0
  849. }
  850. )
  851. item = schema.NumberItem(title='How many dogs',
  852. description='Question',
  853. minimum=0,
  854. maximum=10,
  855. default=0,
  856. enum=(0, 2, 4, 6))
  857. self.assertDictEqual(
  858. item.serialize(), {
  859. 'type': 'number',
  860. 'title': item.title,
  861. 'description': item.description,
  862. 'minimum': item.minimum,
  863. 'maximum': item.maximum,
  864. 'default': 0,
  865. 'enum': [0, 2, 4, 6]
  866. }
  867. )
  868. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  869. def test_number_config_validation(self):
  870. class TestConf(schema.Schema):
  871. item = schema.NumberItem(title='How many dogs', description='Question')
  872. try:
  873. jsonschema.validate({'item': 2}, TestConf.serialize())
  874. except jsonschema.exceptions.ValidationError as exc:
  875. self.fail('ValidationError raised: {0}'.format(exc))
  876. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  877. jsonschema.validate({'item': '3'}, TestConf.serialize())
  878. self.assertIn('is not of type', excinfo.exception.message)
  879. class TestConf(schema.Schema):
  880. item = schema.NumberItem(title='How many dogs',
  881. description='Question',
  882. multiple_of=2.2)
  883. try:
  884. jsonschema.validate({'item': 4.4}, TestConf.serialize())
  885. except jsonschema.exceptions.ValidationError as exc:
  886. self.fail('ValidationError raised: {0}'.format(exc))
  887. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  888. jsonschema.validate({'item': 4}, TestConf.serialize())
  889. self.assertIn('is not a multiple of', excinfo.exception.message)
  890. class TestConf(schema.Schema):
  891. item = schema.NumberItem(title='Foo', description='Foo Item',
  892. minimum=1, maximum=10)
  893. try:
  894. jsonschema.validate({'item': 3}, TestConf.serialize())
  895. except jsonschema.exceptions.ValidationError as exc:
  896. self.fail('ValidationError raised: {0}'.format(exc))
  897. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  898. jsonschema.validate({'item': 11}, TestConf.serialize())
  899. self.assertIn('is greater than the maximum of', excinfo.exception.message)
  900. class TestConf(schema.Schema):
  901. item = schema.NumberItem(title='Foo', description='Foo Item',
  902. minimum=10, maximum=100)
  903. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  904. jsonschema.validate({'item': 3}, TestConf.serialize())
  905. self.assertIn('is less than the minimum of', excinfo.exception.message)
  906. class TestConf(schema.Schema):
  907. item = schema.NumberItem(title='How many dogs',
  908. description='Question',
  909. minimum=0,
  910. exclusive_minimum=True,
  911. maximum=10,
  912. exclusive_maximum=True)
  913. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  914. jsonschema.validate({'item': 0}, TestConf.serialize())
  915. self.assertIn('is less than or equal to the minimum of', excinfo.exception.message)
  916. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  917. jsonschema.validate({'item': 10}, TestConf.serialize())
  918. self.assertIn('is greater than or equal to the maximum of', excinfo.exception.message)
  919. class TestConf(schema.Schema):
  920. item = schema.NumberItem(title='Foo',
  921. description='Foo Item',
  922. enum=(0, 2, 4, 6))
  923. try:
  924. jsonschema.validate({'item': 4}, TestConf.serialize())
  925. except jsonschema.exceptions.ValidationError as exc:
  926. self.fail('ValidationError raised: {0}'.format(exc))
  927. class TestConf(schema.Schema):
  928. item = schema.NumberItem(title='Foo',
  929. description='Foo Item',
  930. enum=(0, 2, 4, 6))
  931. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  932. jsonschema.validate({'item': 3}, TestConf.serialize())
  933. self.assertIn('is not one of', excinfo.exception.message)
  934. def test_integer_config(self):
  935. item = schema.IntegerItem(title='How many dogs', description='Question')
  936. self.assertDictEqual(
  937. item.serialize(), {
  938. 'type': 'integer',
  939. 'title': item.title,
  940. 'description': item.description
  941. }
  942. )
  943. item = schema.IntegerItem(title='How many dogs',
  944. description='Question',
  945. minimum=0,
  946. maximum=10)
  947. self.assertDictEqual(
  948. item.serialize(), {
  949. 'type': 'integer',
  950. 'title': item.title,
  951. 'description': item.description,
  952. 'minimum': item.minimum,
  953. 'maximum': item.maximum
  954. }
  955. )
  956. item = schema.IntegerItem(title='How many dogs',
  957. description='Question',
  958. multiple_of=2)
  959. self.assertDictEqual(
  960. item.serialize(), {
  961. 'type': 'integer',
  962. 'title': item.title,
  963. 'description': item.description,
  964. 'multipleOf': item.multiple_of
  965. }
  966. )
  967. item = schema.IntegerItem(title='How many dogs',
  968. description='Question',
  969. minimum=0,
  970. exclusive_minimum=True,
  971. maximum=10,
  972. exclusive_maximum=True)
  973. self.assertDictEqual(
  974. item.serialize(), {
  975. 'type': 'integer',
  976. 'title': item.title,
  977. 'description': item.description,
  978. 'minimum': item.minimum,
  979. 'maximum': item.maximum,
  980. 'exclusiveMinimum': True,
  981. 'exclusiveMaximum': True
  982. }
  983. )
  984. item = schema.IntegerItem(title='How many dogs',
  985. description='Question',
  986. minimum=0,
  987. maximum=10,
  988. default=0)
  989. self.assertDictEqual(
  990. item.serialize(), {
  991. 'type': 'integer',
  992. 'title': item.title,
  993. 'description': item.description,
  994. 'minimum': item.minimum,
  995. 'maximum': item.maximum,
  996. 'default': 0
  997. }
  998. )
  999. item = schema.IntegerItem(title='How many dogs',
  1000. description='Question',
  1001. minimum=0,
  1002. maximum=10,
  1003. default=0,
  1004. enum=(0, 2, 4, 6))
  1005. self.assertDictEqual(
  1006. item.serialize(), {
  1007. 'type': 'integer',
  1008. 'title': item.title,
  1009. 'description': item.description,
  1010. 'minimum': item.minimum,
  1011. 'maximum': item.maximum,
  1012. 'default': 0,
  1013. 'enum': [0, 2, 4, 6]
  1014. }
  1015. )
  1016. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  1017. def test_integer_config_validation(self):
  1018. class TestConf(schema.Schema):
  1019. item = schema.IntegerItem(title='How many dogs', description='Question')
  1020. try:
  1021. jsonschema.validate({'item': 2}, TestConf.serialize())
  1022. except jsonschema.exceptions.ValidationError as exc:
  1023. self.fail('ValidationError raised: {0}'.format(exc))
  1024. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1025. jsonschema.validate({'item': 3.1}, TestConf.serialize())
  1026. self.assertIn('is not of type', excinfo.exception.message)
  1027. class TestConf(schema.Schema):
  1028. item = schema.IntegerItem(title='How many dogs',
  1029. description='Question',
  1030. multiple_of=2)
  1031. try:
  1032. jsonschema.validate({'item': 4}, TestConf.serialize())
  1033. except jsonschema.exceptions.ValidationError as exc:
  1034. self.fail('ValidationError raised: {0}'.format(exc))
  1035. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1036. jsonschema.validate({'item': 3}, TestConf.serialize())
  1037. self.assertIn('is not a multiple of', excinfo.exception.message)
  1038. class TestConf(schema.Schema):
  1039. item = schema.IntegerItem(title='Foo', description='Foo Item',
  1040. minimum=1, maximum=10)
  1041. try:
  1042. jsonschema.validate({'item': 3}, TestConf.serialize())
  1043. except jsonschema.exceptions.ValidationError as exc:
  1044. self.fail('ValidationError raised: {0}'.format(exc))
  1045. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1046. jsonschema.validate({'item': 11}, TestConf.serialize())
  1047. self.assertIn('is greater than the maximum of', excinfo.exception.message)
  1048. class TestConf(schema.Schema):
  1049. item = schema.IntegerItem(title='Foo', description='Foo Item',
  1050. minimum=10, maximum=100)
  1051. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1052. jsonschema.validate({'item': 3}, TestConf.serialize())
  1053. self.assertIn('is less than the minimum of', excinfo.exception.message)
  1054. class TestConf(schema.Schema):
  1055. item = schema.IntegerItem(title='How many dogs',
  1056. description='Question',
  1057. minimum=0,
  1058. exclusive_minimum=True,
  1059. maximum=10,
  1060. exclusive_maximum=True)
  1061. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1062. jsonschema.validate({'item': 0}, TestConf.serialize())
  1063. self.assertIn('is less than or equal to the minimum of', excinfo.exception.message)
  1064. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1065. jsonschema.validate({'item': 10}, TestConf.serialize())
  1066. self.assertIn('is greater than or equal to the maximum of', excinfo.exception.message)
  1067. class TestConf(schema.Schema):
  1068. item = schema.IntegerItem(title='Foo',
  1069. description='Foo Item',
  1070. enum=(0, 2, 4, 6))
  1071. try:
  1072. jsonschema.validate({'item': 4}, TestConf.serialize())
  1073. except jsonschema.exceptions.ValidationError as exc:
  1074. self.fail('ValidationError raised: {0}'.format(exc))
  1075. class TestConf(schema.Schema):
  1076. item = schema.IntegerItem(title='Foo',
  1077. description='Foo Item',
  1078. enum=(0, 2, 4, 6))
  1079. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1080. jsonschema.validate({'item': 3}, TestConf.serialize())
  1081. self.assertIn('is not one of', excinfo.exception.message)
  1082. def test_array_config(self):
  1083. string_item = schema.StringItem(title='Dog Name',
  1084. description='The dog name')
  1085. item = schema.ArrayItem(title='Dog Names',
  1086. description='Name your dogs',
  1087. items=string_item)
  1088. self.assertDictEqual(
  1089. item.serialize(), {
  1090. 'type': 'array',
  1091. 'title': item.title,
  1092. 'description': item.description,
  1093. 'items': {
  1094. 'type': 'string',
  1095. 'title': string_item.title,
  1096. 'description': string_item.description
  1097. }
  1098. }
  1099. )
  1100. integer_item = schema.IntegerItem(title='Dog Age',
  1101. description='The dog age')
  1102. item = schema.ArrayItem(title='Dog Names',
  1103. description='Name your dogs',
  1104. items=(string_item, integer_item))
  1105. self.assertDictEqual(
  1106. item.serialize(), {
  1107. 'type': 'array',
  1108. 'title': item.title,
  1109. 'description': item.description,
  1110. 'items': [
  1111. {
  1112. 'type': 'string',
  1113. 'title': string_item.title,
  1114. 'description': string_item.description
  1115. },
  1116. {
  1117. 'type': 'integer',
  1118. 'title': integer_item.title,
  1119. 'description': integer_item.description
  1120. }
  1121. ]
  1122. }
  1123. )
  1124. item = schema.ArrayItem(title='Dog Names',
  1125. description='Name your dogs',
  1126. items=(schema.StringItem(),
  1127. schema.IntegerItem()),
  1128. min_items=1,
  1129. max_items=3,
  1130. additional_items=False,
  1131. unique_items=True)
  1132. self.assertDictEqual(
  1133. item.serialize(), {
  1134. 'type': 'array',
  1135. 'title': item.title,
  1136. 'description': item.description,
  1137. 'minItems': item.min_items,
  1138. 'maxItems': item.max_items,
  1139. 'uniqueItems': item.unique_items,
  1140. 'additionalItems': item.additional_items,
  1141. 'items': [
  1142. {
  1143. 'type': 'string',
  1144. },
  1145. {
  1146. 'type': 'integer',
  1147. }
  1148. ]
  1149. }
  1150. )
  1151. class HowManyConfig(schema.Schema):
  1152. item = schema.IntegerItem(title='How many dogs', description='Question')
  1153. item = schema.ArrayItem(title='Dog Names',
  1154. description='Name your dogs',
  1155. items=HowManyConfig())
  1156. self.assertDictEqual(
  1157. item.serialize(), {
  1158. 'type': 'array',
  1159. 'title': item.title,
  1160. 'description': item.description,
  1161. 'items': HowManyConfig.serialize()
  1162. }
  1163. )
  1164. class AgesConfig(schema.Schema):
  1165. item = schema.IntegerItem()
  1166. item = schema.ArrayItem(title='Dog Names',
  1167. description='Name your dogs',
  1168. items=(HowManyConfig(), AgesConfig()))
  1169. self.assertDictEqual(
  1170. item.serialize(), {
  1171. 'type': 'array',
  1172. 'title': item.title,
  1173. 'description': item.description,
  1174. 'items': [
  1175. HowManyConfig.serialize(),
  1176. AgesConfig.serialize()
  1177. ]
  1178. }
  1179. )
  1180. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  1181. def test_array_config_validation(self):
  1182. class TestConf(schema.Schema):
  1183. item = schema.ArrayItem(title='Dog Names',
  1184. description='Name your dogs',
  1185. items=schema.StringItem())
  1186. try:
  1187. jsonschema.validate({'item': ['Tobias', 'Óscar']}, TestConf.serialize(),
  1188. format_checker=jsonschema.FormatChecker())
  1189. except jsonschema.exceptions.ValidationError as exc:
  1190. self.fail('ValidationError raised: {0}'.format(exc))
  1191. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1192. jsonschema.validate({'item': ['Tobias', 'Óscar', 3]}, TestConf.serialize(),
  1193. format_checker=jsonschema.FormatChecker())
  1194. self.assertIn('is not of type', excinfo.exception.message)
  1195. class TestConf(schema.Schema):
  1196. item = schema.ArrayItem(title='Dog Names',
  1197. description='Name your dogs',
  1198. items=schema.StringItem(),
  1199. min_items=1,
  1200. max_items=2)
  1201. try:
  1202. jsonschema.validate({'item': ['Tobias', 'Óscar']}, TestConf.serialize(),
  1203. format_checker=jsonschema.FormatChecker())
  1204. except jsonschema.exceptions.ValidationError as exc:
  1205. self.fail('ValidationError raised: {0}'.format(exc))
  1206. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1207. jsonschema.validate({'item': ['Tobias', 'Óscar', 'Pepe']}, TestConf.serialize(),
  1208. format_checker=jsonschema.FormatChecker())
  1209. self.assertIn('is too long', excinfo.exception.message)
  1210. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1211. jsonschema.validate({'item': []}, TestConf.serialize(),
  1212. format_checker=jsonschema.FormatChecker())
  1213. self.assertIn('is too short', excinfo.exception.message)
  1214. class TestConf(schema.Schema):
  1215. item = schema.ArrayItem(title='Dog Names',
  1216. description='Name your dogs',
  1217. items=schema.StringItem(),
  1218. uniqueItems=True)
  1219. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1220. jsonschema.validate({'item': ['Tobias', 'Tobias']}, TestConf.serialize(),
  1221. format_checker=jsonschema.FormatChecker())
  1222. self.assertIn('has non-unique elements', excinfo.exception.message)
  1223. class TestConf(schema.Schema):
  1224. item = schema.ArrayItem(items=(schema.StringItem(),
  1225. schema.IntegerItem()))
  1226. try:
  1227. jsonschema.validate({'item': ['Óscar', 4]}, TestConf.serialize(),
  1228. format_checker=jsonschema.FormatChecker())
  1229. except jsonschema.exceptions.ValidationError as exc:
  1230. self.fail('ValidationError raised: {0}'.format(exc))
  1231. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1232. jsonschema.validate({'item': ['Tobias', 'Óscar']}, TestConf.serialize(),
  1233. format_checker=jsonschema.FormatChecker())
  1234. self.assertIn('is not of type', excinfo.exception.message)
  1235. class TestConf(schema.Schema):
  1236. item = schema.ArrayItem(
  1237. items=schema.ArrayItem(
  1238. items=(schema.StringItem(),
  1239. schema.IntegerItem())
  1240. )
  1241. )
  1242. try:
  1243. jsonschema.validate({'item': [['Tobias', 8], ['Óscar', 4]]}, TestConf.serialize(),
  1244. format_checker=jsonschema.FormatChecker())
  1245. except jsonschema.exceptions.ValidationError as exc:
  1246. self.fail('ValidationError raised: {0}'.format(exc))
  1247. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1248. jsonschema.validate({'item': [['Tobias', 8], ['Óscar', '4']]}, TestConf.serialize(),
  1249. format_checker=jsonschema.FormatChecker())
  1250. self.assertIn('is not of type', excinfo.exception.message)
  1251. class TestConf(schema.Schema):
  1252. item = schema.ArrayItem(items=schema.StringItem(enum=['Tobias', 'Óscar']))
  1253. try:
  1254. jsonschema.validate({'item': ['Óscar']}, TestConf.serialize(),
  1255. format_checker=jsonschema.FormatChecker())
  1256. except jsonschema.exceptions.ValidationError as exc:
  1257. self.fail('ValidationError raised: {0}'.format(exc))
  1258. try:
  1259. jsonschema.validate({'item': ['Tobias']}, TestConf.serialize(),
  1260. format_checker=jsonschema.FormatChecker())
  1261. except jsonschema.exceptions.ValidationError as exc:
  1262. self.fail('ValidationError raised: {0}'.format(exc))
  1263. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1264. jsonschema.validate({'item': ['Pepe']}, TestConf.serialize(),
  1265. format_checker=jsonschema.FormatChecker())
  1266. self.assertIn('is not one of', excinfo.exception.message)
  1267. def test_dict_config(self):
  1268. item = schema.DictItem(
  1269. title='Poligon',
  1270. description='Describe the Poligon',
  1271. properties={
  1272. 'sides': schema.IntegerItem()
  1273. }
  1274. )
  1275. self.assertDictEqual(
  1276. item.serialize(), {
  1277. 'type': 'object',
  1278. 'title': item.title,
  1279. 'description': item.description,
  1280. 'properties': {
  1281. 'sides': {'type': 'integer'}
  1282. }
  1283. }
  1284. )
  1285. item = schema.DictItem(
  1286. title='Poligon',
  1287. description='Describe the Poligon',
  1288. properties={
  1289. 'sides': schema.IntegerItem()
  1290. },
  1291. min_properties=1,
  1292. max_properties=2
  1293. )
  1294. self.assertDictEqual(
  1295. item.serialize(), {
  1296. 'type': 'object',
  1297. 'title': item.title,
  1298. 'description': item.description,
  1299. 'properties': {
  1300. 'sides': {'type': 'integer'}
  1301. },
  1302. 'minProperties': 1,
  1303. 'maxProperties': 2
  1304. }
  1305. )
  1306. item = schema.DictItem(
  1307. title='Poligon',
  1308. description='Describe the Poligon',
  1309. pattern_properties={
  1310. 's.*': schema.IntegerItem()
  1311. },
  1312. min_properties=1,
  1313. max_properties=2
  1314. )
  1315. self.assertDictEqual(
  1316. item.serialize(), {
  1317. 'type': 'object',
  1318. 'title': item.title,
  1319. 'description': item.description,
  1320. 'patternProperties': {
  1321. 's.*': {'type': 'integer'}
  1322. },
  1323. 'minProperties': 1,
  1324. 'maxProperties': 2
  1325. }
  1326. )
  1327. item = schema.DictItem(
  1328. title='Poligon',
  1329. description='Describe the Poligon',
  1330. properties={
  1331. 'color': schema.StringItem(enum=['red', 'green', 'blue'])
  1332. },
  1333. pattern_properties={
  1334. 's*': schema.IntegerItem()
  1335. },
  1336. min_properties=1,
  1337. max_properties=2
  1338. )
  1339. self.assertDictEqual(
  1340. item.serialize(), {
  1341. 'type': 'object',
  1342. 'title': item.title,
  1343. 'description': item.description,
  1344. 'properties': {
  1345. 'color': {
  1346. 'type': 'string',
  1347. 'enum': ['red', 'green', 'blue']
  1348. }
  1349. },
  1350. 'patternProperties': {
  1351. 's*': {'type': 'integer'}
  1352. },
  1353. 'minProperties': 1,
  1354. 'maxProperties': 2
  1355. }
  1356. )
  1357. item = schema.DictItem(
  1358. title='Poligon',
  1359. description='Describe the Poligon',
  1360. properties={
  1361. 'color': schema.StringItem(enum=['red', 'green', 'blue'])
  1362. },
  1363. pattern_properties={
  1364. 's*': schema.IntegerItem()
  1365. },
  1366. additional_properties=True,
  1367. min_properties=1,
  1368. max_properties=2
  1369. )
  1370. self.assertDictEqual(
  1371. item.serialize(), {
  1372. 'type': 'object',
  1373. 'title': item.title,
  1374. 'description': item.description,
  1375. 'properties': {
  1376. 'color': {
  1377. 'type': 'string',
  1378. 'enum': ['red', 'green', 'blue']
  1379. }
  1380. },
  1381. 'patternProperties': {
  1382. 's*': {'type': 'integer'}
  1383. },
  1384. 'minProperties': 1,
  1385. 'maxProperties': 2,
  1386. 'additionalProperties': True
  1387. }
  1388. )
  1389. item = schema.DictItem(
  1390. title='Poligon',
  1391. description='Describe the Poligon',
  1392. properties={
  1393. 'sides': schema.IntegerItem()
  1394. },
  1395. additional_properties=schema.OneOfItem(items=[schema.BooleanItem(),
  1396. schema.StringItem()])
  1397. )
  1398. self.assertDictEqual(
  1399. item.serialize(), {
  1400. 'type': 'object',
  1401. 'title': item.title,
  1402. 'description': item.description,
  1403. 'properties': {
  1404. 'sides': {'type': 'integer'}
  1405. },
  1406. 'additionalProperties': {
  1407. 'oneOf': [
  1408. {'type': 'boolean'},
  1409. {'type': 'string'}
  1410. ]
  1411. }
  1412. }
  1413. )
  1414. class TestConf(schema.Schema):
  1415. item = schema.DictItem(
  1416. title='Poligon',
  1417. description='Describe the Poligon',
  1418. properties={
  1419. 'sides': schema.IntegerItem(required=True)
  1420. },
  1421. additional_properties=schema.OneOfItem(items=[schema.BooleanItem(),
  1422. schema.StringItem()])
  1423. )
  1424. self.assertDictContainsSubset(
  1425. TestConf.serialize(), {
  1426. '$schema': 'http://json-schema.org/draft-04/schema#',
  1427. 'type': 'object',
  1428. 'properties': {
  1429. 'item': {
  1430. 'title': 'Poligon',
  1431. 'description': 'Describe the Poligon',
  1432. 'type': 'object',
  1433. 'properties': {
  1434. 'sides': {
  1435. 'type': 'integer'
  1436. }
  1437. },
  1438. 'additionalProperties': {
  1439. 'oneOf': [
  1440. {
  1441. 'type': 'boolean'
  1442. },
  1443. {
  1444. 'type': 'string'
  1445. }
  1446. ]
  1447. },
  1448. 'required': [
  1449. 'sides'
  1450. ],
  1451. }
  1452. },
  1453. 'x-ordering': [
  1454. 'item'
  1455. ],
  1456. 'additionalProperties': False
  1457. }
  1458. )
  1459. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  1460. def test_dict_config_validation(self):
  1461. class TestConf(schema.Schema):
  1462. item = schema.DictItem(
  1463. title='Poligon',
  1464. description='Describe the Poligon',
  1465. properties={
  1466. 'sides': schema.IntegerItem()
  1467. }
  1468. )
  1469. try:
  1470. jsonschema.validate({'item': {'sides': 1}}, TestConf.serialize())
  1471. except jsonschema.exceptions.ValidationError as exc:
  1472. self.fail('ValidationError raised: {0}'.format(exc))
  1473. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1474. jsonschema.validate({'item': {'sides': '1'}}, TestConf.serialize())
  1475. self.assertIn('is not of type', excinfo.exception.message)
  1476. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1477. jsonschema.validate({'item': 2}, TestConf.serialize())
  1478. self.assertIn('is not of type', excinfo.exception.message)
  1479. class TestConf(schema.Schema):
  1480. item = schema.DictItem(
  1481. title='Poligon',
  1482. description='Describe the Poligon',
  1483. properties={
  1484. 'color': schema.StringItem(enum=['red', 'green', 'blue'])
  1485. },
  1486. pattern_properties={
  1487. 'si.*': schema.IntegerItem()
  1488. },
  1489. )
  1490. try:
  1491. jsonschema.validate({'item': {'sides': 1, 'color': 'red'}}, TestConf.serialize())
  1492. except jsonschema.exceptions.ValidationError as exc:
  1493. self.fail('ValidationError raised: {0}'.format(exc))
  1494. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1495. jsonschema.validate({'item': {'sides': '4', 'color': 'blue'}}, TestConf.serialize())
  1496. self.assertIn('is not of type', excinfo.exception.message)
  1497. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1498. jsonschema.validate({'item': 2}, TestConf.serialize())
  1499. self.assertIn('is not of type', excinfo.exception.message)
  1500. class TestConf(schema.Schema):
  1501. item = schema.DictItem(
  1502. title='Poligon',
  1503. description='Describe the Poligon',
  1504. properties={
  1505. 'color': schema.StringItem(enum=['red', 'green', 'blue'])
  1506. },
  1507. pattern_properties={
  1508. 'si.*': schema.IntegerItem()
  1509. },
  1510. additional_properties=False
  1511. )
  1512. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1513. jsonschema.validate({'item': {'color': 'green', 'sides': 4, 'surfaces': 4}}, TestConf.serialize())
  1514. if JSONSCHEMA_VERSION < _LooseVersion('2.6.0'):
  1515. self.assertIn(
  1516. 'Additional properties are not allowed',
  1517. excinfo.exception.message)
  1518. else:
  1519. self.assertIn(
  1520. '\'surfaces\' does not match any of the regexes',
  1521. excinfo.exception.message)
  1522. class TestConf(schema.Schema):
  1523. item = schema.DictItem(
  1524. title='Poligon',
  1525. description='Describe the Poligon',
  1526. properties={
  1527. 'color': schema.StringItem(enum=['red', 'green', 'blue'])
  1528. },
  1529. additional_properties=schema.OneOfItem(items=[
  1530. schema.BooleanItem(),
  1531. schema.IntegerItem()
  1532. ])
  1533. )
  1534. try:
  1535. jsonschema.validate({'item': {'sides': 1,
  1536. 'color': 'red',
  1537. 'rugged_surface': False}}, TestConf.serialize())
  1538. except jsonschema.exceptions.ValidationError as exc:
  1539. self.fail('ValidationError raised: {0}'.format(exc))
  1540. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1541. jsonschema.validate({'item': {'sides': '4', 'color': 'blue'}}, TestConf.serialize())
  1542. if JSONSCHEMA_VERSION >= _LooseVersion('3.0.0'):
  1543. self.assertIn("'4'", excinfo.exception.message)
  1544. self.assertIn("is not of type", excinfo.exception.message)
  1545. self.assertIn("'boolean'", excinfo.exception.message)
  1546. else:
  1547. self.assertIn('is not valid under any of the given schemas', excinfo.exception.message)
  1548. class TestConf(schema.Schema):
  1549. item = schema.DictItem(
  1550. title='Poligon',
  1551. description='Describe the Poligon',
  1552. properties={
  1553. 'color': schema.StringItem(enum=['red', 'green', 'blue'])
  1554. },
  1555. additional_properties=schema.OneOfItem(items=[
  1556. schema.BooleanItem(),
  1557. schema.IntegerItem()
  1558. ]),
  1559. min_properties=2,
  1560. max_properties=3
  1561. )
  1562. try:
  1563. jsonschema.validate({'item': {'color': 'red', 'sides': 1}}, TestConf.serialize())
  1564. except jsonschema.exceptions.ValidationError as exc:
  1565. self.fail('ValidationError raised: {0}'.format(exc))
  1566. try:
  1567. jsonschema.validate({'item': {'sides': 1, 'color': 'red', 'rugged_surface': False}}, TestConf.serialize())
  1568. except jsonschema.exceptions.ValidationError as exc:
  1569. self.fail('ValidationError raised: {0}'.format(exc))
  1570. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1571. jsonschema.validate({'item': {'color': 'blue'}}, TestConf.serialize())
  1572. self.assertIn('does not have enough properties', excinfo.exception.message)
  1573. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1574. jsonschema.validate({'item': {'sides': 4,
  1575. 'color': 'blue',
  1576. 'rugged_surface': False,
  1577. 'opaque': True}}, TestConf.serialize())
  1578. self.assertIn('has too many properties', excinfo.exception.message)
  1579. class TestConf(schema.Schema):
  1580. item = schema.DictItem(
  1581. title='Poligon',
  1582. description='Describe the Poligon',
  1583. properties={
  1584. 'sides': schema.IntegerItem(required=True)
  1585. },
  1586. additional_properties=schema.OneOfItem(items=[schema.BooleanItem(),
  1587. schema.StringItem()])
  1588. )
  1589. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1590. jsonschema.validate({'item': {'color': 'blue',
  1591. 'rugged_surface': False,
  1592. 'opaque': True}}, TestConf.serialize())
  1593. self.assertIn('\'sides\' is a required property', excinfo.exception.message)
  1594. class Props(schema.Schema):
  1595. sides = schema.IntegerItem(required=True)
  1596. class TestConf(schema.Schema):
  1597. item = schema.DictItem(
  1598. title='Poligon',
  1599. description='Describe the Poligon',
  1600. properties=Props(),
  1601. additional_properties=schema.OneOfItem(items=[schema.BooleanItem(),
  1602. schema.StringItem()])
  1603. )
  1604. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1605. jsonschema.validate({'item': {'color': 'blue',
  1606. 'rugged_surface': False,
  1607. 'opaque': True}}, TestConf.serialize())
  1608. self.assertIn('\'sides\' is a required property', excinfo.exception.message)
  1609. def test_oneof_config(self):
  1610. item = schema.OneOfItem(
  1611. items=(schema.StringItem(title='Yes', enum=['yes']),
  1612. schema.StringItem(title='No', enum=['no']))
  1613. )
  1614. self.assertEqual(
  1615. item.serialize(), {
  1616. 'oneOf': [i.serialize() for i in item.items]
  1617. }
  1618. )
  1619. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  1620. def test_oneof_config_validation(self):
  1621. class TestConf(schema.Schema):
  1622. item = schema.ArrayItem(
  1623. title='Hungry',
  1624. description='Are you hungry?',
  1625. items=schema.OneOfItem(
  1626. items=(schema.StringItem(title='Yes', enum=['yes']),
  1627. schema.StringItem(title='No', enum=['no']))
  1628. )
  1629. )
  1630. try:
  1631. jsonschema.validate({'item': ['no']}, TestConf.serialize())
  1632. except jsonschema.exceptions.ValidationError as exc:
  1633. self.fail('ValidationError raised: {0}'.format(exc))
  1634. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1635. jsonschema.validate({'item': ['maybe']}, TestConf.serialize())
  1636. if JSONSCHEMA_VERSION >= _LooseVersion('3.0.0'):
  1637. self.assertIn("'maybe'", excinfo.exception.message)
  1638. self.assertIn("is not one of", excinfo.exception.message)
  1639. self.assertIn("'yes'", excinfo.exception.message)
  1640. else:
  1641. self.assertIn('is not valid under any of the given schemas', excinfo.exception.message)
  1642. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1643. jsonschema.validate({'item': 2}, TestConf.serialize())
  1644. self.assertIn('is not of type', excinfo.exception.message)
  1645. def test_anyof_config(self):
  1646. item = schema.AnyOfItem(
  1647. items=(schema.StringItem(title='Yes', enum=['yes']),
  1648. schema.StringItem(title='No', enum=['no']))
  1649. )
  1650. self.assertEqual(
  1651. item.serialize(), {
  1652. 'anyOf': [i.serialize() for i in item.items] # pylint: disable=E1133
  1653. }
  1654. )
  1655. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  1656. def test_anyof_config_validation(self):
  1657. class TestConf(schema.Schema):
  1658. item = schema.ArrayItem(
  1659. title='Hungry',
  1660. description='Are you hungry?',
  1661. items=schema.AnyOfItem(
  1662. items=(schema.StringItem(title='Yes', enum=['yes']),
  1663. schema.StringItem(title='No', enum=['no']),
  1664. schema.BooleanItem())
  1665. )
  1666. )
  1667. try:
  1668. jsonschema.validate({'item': ['no']}, TestConf.serialize())
  1669. except jsonschema.exceptions.ValidationError as exc:
  1670. self.fail('ValidationError raised: {0}'.format(exc))
  1671. try:
  1672. jsonschema.validate({'item': ['yes']}, TestConf.serialize())
  1673. except jsonschema.exceptions.ValidationError as exc:
  1674. self.fail('ValidationError raised: {0}'.format(exc))
  1675. try:
  1676. jsonschema.validate({'item': [True]}, TestConf.serialize())
  1677. except jsonschema.exceptions.ValidationError as exc:
  1678. self.fail('ValidationError raised: {0}'.format(exc))
  1679. try:
  1680. jsonschema.validate({'item': [False]}, TestConf.serialize())
  1681. except jsonschema.exceptions.ValidationError as exc:
  1682. self.fail('ValidationError raised: {0}'.format(exc))
  1683. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1684. jsonschema.validate({'item': ['maybe']}, TestConf.serialize())
  1685. if JSONSCHEMA_VERSION >= _LooseVersion('3.0.0'):
  1686. self.assertIn("'maybe'", excinfo.exception.message)
  1687. self.assertIn("is not one of", excinfo.exception.message)
  1688. self.assertIn("'yes'", excinfo.exception.message)
  1689. else:
  1690. self.assertIn('is not valid under any of the given schemas', excinfo.exception.message)
  1691. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1692. jsonschema.validate({'item': 2}, TestConf.serialize())
  1693. self.assertIn('is not of type', excinfo.exception.message)
  1694. def test_allof_config(self):
  1695. item = schema.AllOfItem(
  1696. items=(schema.StringItem(min_length=2),
  1697. schema.StringItem(max_length=3))
  1698. )
  1699. self.assertEqual(
  1700. item.serialize(), {
  1701. 'allOf': [i.serialize() for i in item.items] # pylint: disable=E1133
  1702. }
  1703. )
  1704. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  1705. def test_allof_config_validation(self):
  1706. class TestConf(schema.Schema):
  1707. item = schema.ArrayItem(
  1708. title='Hungry',
  1709. description='Are you hungry?',
  1710. items=schema.AllOfItem(
  1711. items=(schema.StringItem(min_length=2),
  1712. schema.StringItem(max_length=3))
  1713. )
  1714. )
  1715. try:
  1716. jsonschema.validate({'item': ['no']}, TestConf.serialize())
  1717. except jsonschema.exceptions.ValidationError as exc:
  1718. self.fail('ValidationError raised: {0}'.format(exc))
  1719. try:
  1720. jsonschema.validate({'item': ['yes']}, TestConf.serialize())
  1721. except jsonschema.exceptions.ValidationError as exc:
  1722. self.fail('ValidationError raised: {0}'.format(exc))
  1723. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1724. jsonschema.validate({'item': ['maybe']}, TestConf.serialize())
  1725. self.assertIn('is too long', excinfo.exception.message)
  1726. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1727. jsonschema.validate({'item': ['hmmmm']}, TestConf.serialize())
  1728. self.assertIn('is too long', excinfo.exception.message)
  1729. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1730. jsonschema.validate({'item': 2}, TestConf.serialize())
  1731. self.assertIn('is not of type', excinfo.exception.message)
  1732. def test_not_config(self):
  1733. item = schema.NotItem(item=schema.BooleanItem())
  1734. self.assertEqual(
  1735. item.serialize(), {
  1736. 'not': item.item.serialize()
  1737. }
  1738. )
  1739. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  1740. def test_not_config_validation(self):
  1741. class TestConf(schema.Schema):
  1742. item = schema.ArrayItem(
  1743. title='Hungry',
  1744. description='Are you hungry?',
  1745. items=schema.NotItem(item=schema.BooleanItem())
  1746. )
  1747. try:
  1748. jsonschema.validate({'item': ['no']}, TestConf.serialize())
  1749. except jsonschema.exceptions.ValidationError as exc:
  1750. self.fail('ValidationError raised: {0}'.format(exc))
  1751. try:
  1752. jsonschema.validate({'item': ['yes']}, TestConf.serialize())
  1753. except jsonschema.exceptions.ValidationError as exc:
  1754. self.fail('ValidationError raised: {0}'.format(exc))
  1755. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1756. jsonschema.validate({'item': [True]}, TestConf.serialize())
  1757. self.assertIn('is not allowed for', excinfo.exception.message)
  1758. with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
  1759. jsonschema.validate({'item': [False]}, TestConf.serialize())
  1760. self.assertIn('is not allowed for', excinfo.exception.message)
  1761. def test_item_name_override_class_attrname(self):
  1762. class TestConf(schema.Schema):
  1763. item = schema.BooleanItem(name='hungry', title='Hungry', description='Are you hungry?')
  1764. expected = {
  1765. "$schema": "http://json-schema.org/draft-04/schema#",
  1766. "type": "object",
  1767. "properties": {
  1768. "hungry": {
  1769. "type": "boolean",
  1770. "description": "Are you hungry?",
  1771. "title": "Hungry"
  1772. }
  1773. },
  1774. "x-ordering": [
  1775. "hungry"
  1776. ],
  1777. "additionalProperties": False
  1778. }
  1779. self.assertDictEqual(TestConf.serialize(), expected)
  1780. def test_config_name_override_class_attrname(self):
  1781. class TestConf(schema.Schema):
  1782. item = schema.BooleanItem(title='Hungry', description='Are you hungry?')
  1783. class TestConf2(schema.Schema):
  1784. a_name = TestConf(name='another_name')
  1785. expected = {
  1786. "$schema": "http://json-schema.org/draft-04/schema#",
  1787. "type": "object",
  1788. "properties": {
  1789. "another_name": {
  1790. "id": "https://non-existing.saltstack.com/schemas/another_name.json#",
  1791. "type": "object",
  1792. "properties": {
  1793. "item": {
  1794. "type": "boolean",
  1795. "description": "Are you hungry?",
  1796. "title": "Hungry"
  1797. }
  1798. },
  1799. "x-ordering": [
  1800. "item"
  1801. ],
  1802. "additionalProperties": False
  1803. }
  1804. },
  1805. "x-ordering": [
  1806. "another_name"
  1807. ],
  1808. "additionalProperties": False
  1809. }
  1810. self.assertDictEqual(TestConf2.serialize(), expected)
  1811. class ComplexSchemaItem(schema.ComplexSchemaItem):
  1812. _complex_attributes = ['thirsty']
  1813. thirsty = schema.BooleanItem(title='Thirsty',
  1814. description='Are you thirsty?')
  1815. class ComplexComplexSchemaItem(schema.ComplexSchemaItem):
  1816. _complex_attributes = ['hungry', 'complex_item']
  1817. hungry = schema.BooleanItem(title='Hungry',
  1818. description='Are you hungry?',
  1819. required=True)
  1820. complex_item = ComplexSchemaItem(definition_name='test_definition')
  1821. class TestComplexDefinitionsSchema(schema.DefinitionsSchema):
  1822. title = 'Test Complex Definition Schema'
  1823. complex_item = ComplexSchemaItem()
  1824. class TestOneOfComplexDefinitionsSchema(schema.DefinitionsSchema):
  1825. title = 'Test OneOf Complex Definitions Schema'
  1826. one_of_item = schema.OneOfItem(
  1827. items=[ComplexSchemaItem(), schema.StringItem()])
  1828. class TestArrayComplexDefinitionsSchema(schema.DefinitionsSchema):
  1829. title = 'Test Array Complex Definitions Schema'
  1830. array_item = schema.ArrayItem(items=ComplexSchemaItem())
  1831. class TestDictComplexDefinitionsSchema(schema.DefinitionsSchema):
  1832. title = 'Test Dict Complex Definitions Schema'
  1833. dict_item = schema.DictItem(
  1834. properties={'complex_obj': ComplexSchemaItem(required=True)},
  1835. additional_properties=ComplexSchemaItem())
  1836. class TestComplexComplexDefinitionsSchema(schema.DefinitionsSchema):
  1837. title = 'Test Complex Complex Definition Schema'
  1838. complex_complex_item = ComplexComplexSchemaItem()
  1839. class ComplexSchemaTestCase(TestCase):
  1840. ''' Test cases with definition schemas containing complex items'''
  1841. obj = ComplexSchemaItem()
  1842. complex_obj = ComplexComplexSchemaItem()
  1843. schema = TestComplexDefinitionsSchema()
  1844. one_of_schema = TestOneOfComplexDefinitionsSchema()
  1845. array_schema = TestArrayComplexDefinitionsSchema()
  1846. dict_schema = TestDictComplexDefinitionsSchema()
  1847. complex_schema = TestComplexComplexDefinitionsSchema()
  1848. def test_complex_schema_item_serialize(self):
  1849. obj = copy.deepcopy(self.obj)
  1850. expected_serialized = {'$ref':
  1851. '#/definitions/ComplexSchemaItem'}
  1852. self.assertDictEqual(obj.serialize(), expected_serialized)
  1853. def test_complex_schema_item_definition(self):
  1854. obj = copy.deepcopy(self.obj)
  1855. expected_def = {
  1856. 'type': 'object',
  1857. 'title': 'ComplexSchemaItem',
  1858. 'properties': {
  1859. 'thirsty': {
  1860. 'type': 'boolean',
  1861. 'title': 'Thirsty',
  1862. 'description': 'Are you thirsty?'}}}
  1863. self.assertDictEqual(obj.get_definition(), expected_def)
  1864. def test_complex_complex_schema_item_definition(self):
  1865. complex_obj = copy.deepcopy(self.complex_obj)
  1866. expected_def = {
  1867. 'type': 'object',
  1868. 'title': 'ComplexComplexSchemaItem',
  1869. 'properties': {
  1870. 'hungry': {
  1871. 'type': 'boolean',
  1872. 'title': 'Hungry',
  1873. 'description': 'Are you hungry?'},
  1874. 'complex_item': {
  1875. 'type': 'object',
  1876. '$ref': '#/definitions/test_definition'}},
  1877. 'required': ['hungry']}
  1878. self.assertDictEqual(complex_obj.get_definition(), expected_def)
  1879. def test_complex_definition_schema(self):
  1880. serialized = salt.utils.yaml.safe_load(salt.utils.json.dumps(self.schema.serialize()))
  1881. expected = {
  1882. '$schema': 'http://json-schema.org/draft-04/schema#',
  1883. 'title': 'Test Complex Definition Schema',
  1884. 'type': 'object',
  1885. 'properties': {
  1886. 'complex_item': {
  1887. '$ref': '#/definitions/ComplexSchemaItem'}},
  1888. 'x-ordering': ['complex_item'],
  1889. 'additionalProperties': False,
  1890. 'definitions': {
  1891. 'ComplexSchemaItem': {
  1892. 'type': 'object',
  1893. 'title': 'ComplexSchemaItem',
  1894. 'properties': {
  1895. 'thirsty': {
  1896. 'type': 'boolean',
  1897. 'title': 'Thirsty',
  1898. 'description': 'Are you thirsty?'}}}}}
  1899. self.assertDictEqual(serialized, expected)
  1900. def test_one_of_complex_definition_schema(self):
  1901. serialized = salt.utils.yaml.safe_load(salt.utils.json.dumps(self.one_of_schema.serialize()))
  1902. expected = {
  1903. '$schema': 'http://json-schema.org/draft-04/schema#',
  1904. 'title': 'Test OneOf Complex Definitions Schema',
  1905. 'type': 'object',
  1906. 'properties': {
  1907. 'one_of_item': {
  1908. 'oneOf': [{'$ref': '#/definitions/ComplexSchemaItem'},
  1909. {'type': 'string'}]}},
  1910. 'x-ordering': ['one_of_item'],
  1911. 'additionalProperties': False,
  1912. 'definitions': {
  1913. 'ComplexSchemaItem': {
  1914. 'type': 'object',
  1915. 'title': 'ComplexSchemaItem',
  1916. 'properties': {
  1917. 'thirsty': {
  1918. 'type': 'boolean',
  1919. 'title': 'Thirsty',
  1920. 'description': 'Are you thirsty?'}}}}}
  1921. self.assertDictEqual(serialized, expected)
  1922. def test_array_complex_definition_schema(self):
  1923. serialized = salt.utils.yaml.safe_load(salt.utils.json.dumps(self.array_schema.serialize()))
  1924. expected = {
  1925. '$schema': 'http://json-schema.org/draft-04/schema#',
  1926. 'title': 'Test Array Complex Definitions Schema',
  1927. 'type': 'object',
  1928. 'properties': {
  1929. 'array_item': {
  1930. 'type': 'array',
  1931. 'title': 'array_item',
  1932. 'items': {'$ref': '#/definitions/ComplexSchemaItem'}}},
  1933. 'x-ordering': ['array_item'],
  1934. 'additionalProperties': False,
  1935. 'definitions': {
  1936. 'ComplexSchemaItem': {
  1937. 'type': 'object',
  1938. 'title': 'ComplexSchemaItem',
  1939. 'properties': {
  1940. 'thirsty': {
  1941. 'type': 'boolean',
  1942. 'title': 'Thirsty',
  1943. 'description': 'Are you thirsty?'}}}}}
  1944. self.assertDictEqual(serialized, expected)
  1945. def test_dict_complex_definition_schema(self):
  1946. serialized = salt.utils.yaml.safe_load(salt.utils.json.dumps(self.dict_schema.serialize()))
  1947. expected = {
  1948. '$schema': 'http://json-schema.org/draft-04/schema#',
  1949. 'title': 'Test Dict Complex Definitions Schema',
  1950. 'type': 'object',
  1951. 'properties': {
  1952. 'dict_item': {
  1953. 'type': 'object',
  1954. 'title': 'dict_item',
  1955. 'required': ['complex_obj'],
  1956. 'properties':
  1957. {'complex_obj':
  1958. {'$ref': '#/definitions/ComplexSchemaItem'}},
  1959. 'additionalProperties':
  1960. {'$ref': '#/definitions/ComplexSchemaItem'}}},
  1961. 'x-ordering': ['dict_item'],
  1962. 'additionalProperties': False,
  1963. 'definitions': {
  1964. 'ComplexSchemaItem': {
  1965. 'type': 'object',
  1966. 'title': 'ComplexSchemaItem',
  1967. 'properties': {
  1968. 'thirsty': {
  1969. 'type': 'boolean',
  1970. 'title': 'Thirsty',
  1971. 'description': 'Are you thirsty?'}}}}}
  1972. self.assertDictEqual(serialized, expected)
  1973. def test_complex_complex_definition_schema(self):
  1974. serialized = salt.utils.yaml.safe_load(salt.utils.json.dumps(
  1975. self.complex_schema.serialize()
  1976. ))
  1977. expected = {
  1978. '$schema': 'http://json-schema.org/draft-04/schema#',
  1979. 'title': 'Test Complex Complex Definition Schema',
  1980. 'type': 'object',
  1981. 'properties': {
  1982. 'complex_complex_item': {
  1983. '$ref': '#/definitions/ComplexComplexSchemaItem'}},
  1984. 'x-ordering': ['complex_complex_item'],
  1985. 'additionalProperties': False,
  1986. 'definitions': {
  1987. 'ComplexComplexSchemaItem': {
  1988. 'type': 'object',
  1989. 'title': 'ComplexComplexSchemaItem',
  1990. 'properties': {
  1991. 'hungry': {
  1992. 'type': 'boolean',
  1993. 'title': 'Hungry',
  1994. 'description': 'Are you hungry?'},
  1995. 'complex_item': {
  1996. 'type': 'object',
  1997. '$ref': '#/definitions/test_definition'}},
  1998. 'required': ['hungry']},
  1999. 'test_definition': {
  2000. 'type': 'object',
  2001. 'title': 'test_definition',
  2002. 'properties': {
  2003. 'thirsty': {
  2004. 'type': 'boolean',
  2005. 'title': 'Thirsty',
  2006. 'description': 'Are you thirsty?'}}}}}
  2007. self.assertDictEqual(serialized, expected)
  2008. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  2009. def test_complex_schema_item_thirsty_valid(self):
  2010. serialized = self.schema.serialize()
  2011. try:
  2012. jsonschema.validate({'complex_item': {'thirsty': True}},
  2013. serialized)
  2014. except jsonschema.exceptions.ValidationError as exc:
  2015. self.fail('ValidationError raised: {0}'.format(exc))
  2016. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  2017. def test_complex_schema_item_thirsty_invalid(self):
  2018. serialized = self.schema.serialize()
  2019. with self.assertRaises(jsonschema.exceptions.ValidationError) \
  2020. as excinfo:
  2021. jsonschema.validate({'complex_item': {'thirsty': 'Foo'}},
  2022. serialized)
  2023. expected = "u'Foo' is not of type u'boolean'" if six.PY2 \
  2024. else "'Foo' is not of type 'boolean'"
  2025. self.assertIn(expected, excinfo.exception.message)
  2026. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  2027. def test_complex_complex_schema_item_hungry_valid(self):
  2028. serialized = self.complex_schema.serialize()
  2029. try:
  2030. jsonschema.validate({'complex_complex_item': {'hungry': True}},
  2031. serialized)
  2032. except jsonschema.exceptions.ValidationError as exc:
  2033. self.fail('ValidationError raised: {0}'.format(exc))
  2034. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  2035. def test_both_complex_complex_schema_all_items_valid(self):
  2036. serialized = self.complex_schema.serialize()
  2037. try:
  2038. jsonschema.validate({'complex_complex_item':
  2039. {'hungry': True,
  2040. 'complex_item': {'thirsty': True}}},
  2041. serialized)
  2042. except jsonschema.exceptions.ValidationError as exc:
  2043. self.fail('ValidationError raised: {0}'.format(exc))
  2044. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  2045. def test_complex_complex_schema_item_hungry_invalid(self):
  2046. serialized = self.complex_schema.serialize()
  2047. with self.assertRaises(jsonschema.exceptions.ValidationError) \
  2048. as excinfo:
  2049. jsonschema.validate({'complex_complex_item': {'hungry': 'Foo'}},
  2050. serialized)
  2051. expected = "u'Foo' is not of type u'boolean'" if six.PY2 \
  2052. else "'Foo' is not of type 'boolean'"
  2053. self.assertIn(expected, excinfo.exception.message)
  2054. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  2055. def test_complex_complex_schema_item_inner_thirsty_invalid(self):
  2056. serialized = self.complex_schema.serialize()
  2057. with self.assertRaises(jsonschema.exceptions.ValidationError) \
  2058. as excinfo:
  2059. jsonschema.validate(
  2060. {'complex_complex_item': {'hungry': True,
  2061. 'complex_item': {'thirsty': 'Bar'}}},
  2062. serialized)
  2063. expected = "u'Bar' is not of type u'boolean'" if six.PY2 \
  2064. else "'Bar' is not of type 'boolean'"
  2065. self.assertIn(expected, excinfo.exception.message)
  2066. @skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
  2067. def test_complex_complex_schema_item_missing_required_hungry(self):
  2068. serialized = self.complex_schema.serialize()
  2069. with self.assertRaises(jsonschema.exceptions.ValidationError) \
  2070. as excinfo:
  2071. jsonschema.validate(
  2072. {'complex_complex_item': {'complex_item': {'thirsty': True}}},
  2073. serialized)
  2074. self.assertIn('\'hungry\' is a required property',
  2075. excinfo.exception.message)