test_cli.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. from contextlib import redirect_stderr, redirect_stdout
  2. from importlib import metadata
  3. from io import StringIO
  4. from json import JSONDecodeError
  5. from pathlib import Path
  6. from textwrap import dedent
  7. from unittest import TestCase
  8. import json
  9. import os
  10. import subprocess
  11. import sys
  12. import tempfile
  13. import warnings
  14. from jsonschema import Draft4Validator, Draft202012Validator
  15. from jsonschema.exceptions import (
  16. SchemaError,
  17. ValidationError,
  18. _RefResolutionError,
  19. )
  20. from jsonschema.validators import _LATEST_VERSION, validate
  21. with warnings.catch_warnings():
  22. warnings.simplefilter("ignore")
  23. from jsonschema import cli
  24. def fake_validator(*errors):
  25. errors = list(reversed(errors))
  26. class FakeValidator:
  27. def __init__(self, *args, **kwargs):
  28. pass
  29. def iter_errors(self, instance):
  30. if errors:
  31. return errors.pop()
  32. return [] # pragma: no cover
  33. @classmethod
  34. def check_schema(self, schema):
  35. pass
  36. return FakeValidator
  37. def fake_open(all_contents):
  38. def open(path):
  39. contents = all_contents.get(path)
  40. if contents is None:
  41. raise FileNotFoundError(path)
  42. return StringIO(contents)
  43. return open
  44. def _message_for(non_json):
  45. try:
  46. json.loads(non_json)
  47. except JSONDecodeError as error:
  48. return str(error)
  49. else: # pragma: no cover
  50. raise RuntimeError("Tried and failed to capture a JSON dump error.")
  51. class TestCLI(TestCase):
  52. def run_cli(
  53. self, argv, files=None, stdin=StringIO(), exit_code=0, **override,
  54. ):
  55. arguments = cli.parse_args(argv)
  56. arguments.update(override)
  57. self.assertFalse(hasattr(cli, "open"))
  58. cli.open = fake_open(files or {})
  59. try:
  60. stdout, stderr = StringIO(), StringIO()
  61. actual_exit_code = cli.run(
  62. arguments,
  63. stdin=stdin,
  64. stdout=stdout,
  65. stderr=stderr,
  66. )
  67. finally:
  68. del cli.open
  69. self.assertEqual(
  70. actual_exit_code, exit_code, msg=dedent(
  71. f"""
  72. Expected an exit code of {exit_code} != {actual_exit_code}.
  73. stdout: {stdout.getvalue()}
  74. stderr: {stderr.getvalue()}
  75. """,
  76. ),
  77. )
  78. return stdout.getvalue(), stderr.getvalue()
  79. def assertOutputs(self, stdout="", stderr="", **kwargs):
  80. self.assertEqual(
  81. self.run_cli(**kwargs),
  82. (dedent(stdout), dedent(stderr)),
  83. )
  84. def test_invalid_instance(self):
  85. error = ValidationError("I am an error!", instance=12)
  86. self.assertOutputs(
  87. files=dict(
  88. some_schema='{"does not": "matter since it is stubbed"}',
  89. some_instance=json.dumps(error.instance),
  90. ),
  91. validator=fake_validator([error]),
  92. argv=["-i", "some_instance", "some_schema"],
  93. exit_code=1,
  94. stderr="12: I am an error!\n",
  95. )
  96. def test_invalid_instance_pretty_output(self):
  97. error = ValidationError("I am an error!", instance=12)
  98. self.assertOutputs(
  99. files=dict(
  100. some_schema='{"does not": "matter since it is stubbed"}',
  101. some_instance=json.dumps(error.instance),
  102. ),
  103. validator=fake_validator([error]),
  104. argv=["-i", "some_instance", "--output", "pretty", "some_schema"],
  105. exit_code=1,
  106. stderr="""\
  107. ===[ValidationError]===(some_instance)===
  108. I am an error!
  109. -----------------------------
  110. """,
  111. )
  112. def test_invalid_instance_explicit_plain_output(self):
  113. error = ValidationError("I am an error!", instance=12)
  114. self.assertOutputs(
  115. files=dict(
  116. some_schema='{"does not": "matter since it is stubbed"}',
  117. some_instance=json.dumps(error.instance),
  118. ),
  119. validator=fake_validator([error]),
  120. argv=["--output", "plain", "-i", "some_instance", "some_schema"],
  121. exit_code=1,
  122. stderr="12: I am an error!\n",
  123. )
  124. def test_invalid_instance_multiple_errors(self):
  125. instance = 12
  126. first = ValidationError("First error", instance=instance)
  127. second = ValidationError("Second error", instance=instance)
  128. self.assertOutputs(
  129. files=dict(
  130. some_schema='{"does not": "matter since it is stubbed"}',
  131. some_instance=json.dumps(instance),
  132. ),
  133. validator=fake_validator([first, second]),
  134. argv=["-i", "some_instance", "some_schema"],
  135. exit_code=1,
  136. stderr="""\
  137. 12: First error
  138. 12: Second error
  139. """,
  140. )
  141. def test_invalid_instance_multiple_errors_pretty_output(self):
  142. instance = 12
  143. first = ValidationError("First error", instance=instance)
  144. second = ValidationError("Second error", instance=instance)
  145. self.assertOutputs(
  146. files=dict(
  147. some_schema='{"does not": "matter since it is stubbed"}',
  148. some_instance=json.dumps(instance),
  149. ),
  150. validator=fake_validator([first, second]),
  151. argv=["-i", "some_instance", "--output", "pretty", "some_schema"],
  152. exit_code=1,
  153. stderr="""\
  154. ===[ValidationError]===(some_instance)===
  155. First error
  156. -----------------------------
  157. ===[ValidationError]===(some_instance)===
  158. Second error
  159. -----------------------------
  160. """,
  161. )
  162. def test_multiple_invalid_instances(self):
  163. first_instance = 12
  164. first_errors = [
  165. ValidationError("An error", instance=first_instance),
  166. ValidationError("Another error", instance=first_instance),
  167. ]
  168. second_instance = "foo"
  169. second_errors = [ValidationError("BOOM", instance=second_instance)]
  170. self.assertOutputs(
  171. files=dict(
  172. some_schema='{"does not": "matter since it is stubbed"}',
  173. some_first_instance=json.dumps(first_instance),
  174. some_second_instance=json.dumps(second_instance),
  175. ),
  176. validator=fake_validator(first_errors, second_errors),
  177. argv=[
  178. "-i", "some_first_instance",
  179. "-i", "some_second_instance",
  180. "some_schema",
  181. ],
  182. exit_code=1,
  183. stderr="""\
  184. 12: An error
  185. 12: Another error
  186. foo: BOOM
  187. """,
  188. )
  189. def test_multiple_invalid_instances_pretty_output(self):
  190. first_instance = 12
  191. first_errors = [
  192. ValidationError("An error", instance=first_instance),
  193. ValidationError("Another error", instance=first_instance),
  194. ]
  195. second_instance = "foo"
  196. second_errors = [ValidationError("BOOM", instance=second_instance)]
  197. self.assertOutputs(
  198. files=dict(
  199. some_schema='{"does not": "matter since it is stubbed"}',
  200. some_first_instance=json.dumps(first_instance),
  201. some_second_instance=json.dumps(second_instance),
  202. ),
  203. validator=fake_validator(first_errors, second_errors),
  204. argv=[
  205. "--output", "pretty",
  206. "-i", "some_first_instance",
  207. "-i", "some_second_instance",
  208. "some_schema",
  209. ],
  210. exit_code=1,
  211. stderr="""\
  212. ===[ValidationError]===(some_first_instance)===
  213. An error
  214. -----------------------------
  215. ===[ValidationError]===(some_first_instance)===
  216. Another error
  217. -----------------------------
  218. ===[ValidationError]===(some_second_instance)===
  219. BOOM
  220. -----------------------------
  221. """,
  222. )
  223. def test_custom_error_format(self):
  224. first_instance = 12
  225. first_errors = [
  226. ValidationError("An error", instance=first_instance),
  227. ValidationError("Another error", instance=first_instance),
  228. ]
  229. second_instance = "foo"
  230. second_errors = [ValidationError("BOOM", instance=second_instance)]
  231. self.assertOutputs(
  232. files=dict(
  233. some_schema='{"does not": "matter since it is stubbed"}',
  234. some_first_instance=json.dumps(first_instance),
  235. some_second_instance=json.dumps(second_instance),
  236. ),
  237. validator=fake_validator(first_errors, second_errors),
  238. argv=[
  239. "--error-format", ":{error.message}._-_.{error.instance}:",
  240. "-i", "some_first_instance",
  241. "-i", "some_second_instance",
  242. "some_schema",
  243. ],
  244. exit_code=1,
  245. stderr=":An error._-_.12::Another error._-_.12::BOOM._-_.foo:",
  246. )
  247. def test_invalid_schema(self):
  248. self.assertOutputs(
  249. files=dict(some_schema='{"type": 12}'),
  250. argv=["some_schema"],
  251. exit_code=1,
  252. stderr="""\
  253. 12: 12 is not valid under any of the given schemas
  254. """,
  255. )
  256. def test_invalid_schema_pretty_output(self):
  257. schema = {"type": 12}
  258. with self.assertRaises(SchemaError) as e:
  259. validate(schema=schema, instance="")
  260. error = str(e.exception)
  261. self.assertOutputs(
  262. files=dict(some_schema=json.dumps(schema)),
  263. argv=["--output", "pretty", "some_schema"],
  264. exit_code=1,
  265. stderr=(
  266. "===[SchemaError]===(some_schema)===\n\n"
  267. + str(error)
  268. + "\n-----------------------------\n"
  269. ),
  270. )
  271. def test_invalid_schema_multiple_errors(self):
  272. self.assertOutputs(
  273. files=dict(some_schema='{"type": 12, "items": 57}'),
  274. argv=["some_schema"],
  275. exit_code=1,
  276. stderr="""\
  277. 57: 57 is not of type 'object', 'boolean'
  278. """,
  279. )
  280. def test_invalid_schema_multiple_errors_pretty_output(self):
  281. schema = {"type": 12, "items": 57}
  282. with self.assertRaises(SchemaError) as e:
  283. validate(schema=schema, instance="")
  284. error = str(e.exception)
  285. self.assertOutputs(
  286. files=dict(some_schema=json.dumps(schema)),
  287. argv=["--output", "pretty", "some_schema"],
  288. exit_code=1,
  289. stderr=(
  290. "===[SchemaError]===(some_schema)===\n\n"
  291. + str(error)
  292. + "\n-----------------------------\n"
  293. ),
  294. )
  295. def test_invalid_schema_with_invalid_instance(self):
  296. """
  297. "Validating" an instance that's invalid under an invalid schema
  298. just shows the schema error.
  299. """
  300. self.assertOutputs(
  301. files=dict(
  302. some_schema='{"type": 12, "minimum": 30}',
  303. some_instance="13",
  304. ),
  305. argv=["-i", "some_instance", "some_schema"],
  306. exit_code=1,
  307. stderr="""\
  308. 12: 12 is not valid under any of the given schemas
  309. """,
  310. )
  311. def test_invalid_schema_with_invalid_instance_pretty_output(self):
  312. instance, schema = 13, {"type": 12, "minimum": 30}
  313. with self.assertRaises(SchemaError) as e:
  314. validate(schema=schema, instance=instance)
  315. error = str(e.exception)
  316. self.assertOutputs(
  317. files=dict(
  318. some_schema=json.dumps(schema),
  319. some_instance=json.dumps(instance),
  320. ),
  321. argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
  322. exit_code=1,
  323. stderr=(
  324. "===[SchemaError]===(some_schema)===\n\n"
  325. + str(error)
  326. + "\n-----------------------------\n"
  327. ),
  328. )
  329. def test_invalid_instance_continues_with_the_rest(self):
  330. self.assertOutputs(
  331. files=dict(
  332. some_schema='{"minimum": 30}',
  333. first_instance="not valid JSON!",
  334. second_instance="12",
  335. ),
  336. argv=[
  337. "-i", "first_instance",
  338. "-i", "second_instance",
  339. "some_schema",
  340. ],
  341. exit_code=1,
  342. stderr="""\
  343. Failed to parse 'first_instance': {}
  344. 12: 12 is less than the minimum of 30
  345. """.format(_message_for("not valid JSON!")),
  346. )
  347. def test_custom_error_format_applies_to_schema_errors(self):
  348. instance, schema = 13, {"type": 12, "minimum": 30}
  349. with self.assertRaises(SchemaError):
  350. validate(schema=schema, instance=instance)
  351. self.assertOutputs(
  352. files=dict(some_schema=json.dumps(schema)),
  353. argv=[
  354. "--error-format", ":{error.message}._-_.{error.instance}:",
  355. "some_schema",
  356. ],
  357. exit_code=1,
  358. stderr=":12 is not valid under any of the given schemas._-_.12:",
  359. )
  360. def test_instance_is_invalid_JSON(self):
  361. instance = "not valid JSON!"
  362. self.assertOutputs(
  363. files=dict(some_schema="{}", some_instance=instance),
  364. argv=["-i", "some_instance", "some_schema"],
  365. exit_code=1,
  366. stderr=f"""\
  367. Failed to parse 'some_instance': {_message_for(instance)}
  368. """,
  369. )
  370. def test_instance_is_invalid_JSON_pretty_output(self):
  371. stdout, stderr = self.run_cli(
  372. files=dict(
  373. some_schema="{}",
  374. some_instance="not valid JSON!",
  375. ),
  376. argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
  377. exit_code=1,
  378. )
  379. self.assertFalse(stdout)
  380. self.assertIn(
  381. "(some_instance)===\n\nTraceback (most recent call last):\n",
  382. stderr,
  383. )
  384. self.assertNotIn("some_schema", stderr)
  385. def test_instance_is_invalid_JSON_on_stdin(self):
  386. instance = "not valid JSON!"
  387. self.assertOutputs(
  388. files=dict(some_schema="{}"),
  389. stdin=StringIO(instance),
  390. argv=["some_schema"],
  391. exit_code=1,
  392. stderr=f"""\
  393. Failed to parse <stdin>: {_message_for(instance)}
  394. """,
  395. )
  396. def test_instance_is_invalid_JSON_on_stdin_pretty_output(self):
  397. stdout, stderr = self.run_cli(
  398. files=dict(some_schema="{}"),
  399. stdin=StringIO("not valid JSON!"),
  400. argv=["--output", "pretty", "some_schema"],
  401. exit_code=1,
  402. )
  403. self.assertFalse(stdout)
  404. self.assertIn(
  405. "(<stdin>)===\n\nTraceback (most recent call last):\n",
  406. stderr,
  407. )
  408. self.assertNotIn("some_schema", stderr)
  409. def test_schema_is_invalid_JSON(self):
  410. schema = "not valid JSON!"
  411. self.assertOutputs(
  412. files=dict(some_schema=schema),
  413. argv=["some_schema"],
  414. exit_code=1,
  415. stderr=f"""\
  416. Failed to parse 'some_schema': {_message_for(schema)}
  417. """,
  418. )
  419. def test_schema_is_invalid_JSON_pretty_output(self):
  420. stdout, stderr = self.run_cli(
  421. files=dict(some_schema="not valid JSON!"),
  422. argv=["--output", "pretty", "some_schema"],
  423. exit_code=1,
  424. )
  425. self.assertFalse(stdout)
  426. self.assertIn(
  427. "(some_schema)===\n\nTraceback (most recent call last):\n",
  428. stderr,
  429. )
  430. def test_schema_and_instance_are_both_invalid_JSON(self):
  431. """
  432. Only the schema error is reported, as we abort immediately.
  433. """
  434. schema, instance = "not valid JSON!", "also not valid JSON!"
  435. self.assertOutputs(
  436. files=dict(some_schema=schema, some_instance=instance),
  437. argv=["some_schema"],
  438. exit_code=1,
  439. stderr=f"""\
  440. Failed to parse 'some_schema': {_message_for(schema)}
  441. """,
  442. )
  443. def test_schema_and_instance_are_both_invalid_JSON_pretty_output(self):
  444. """
  445. Only the schema error is reported, as we abort immediately.
  446. """
  447. stdout, stderr = self.run_cli(
  448. files=dict(
  449. some_schema="not valid JSON!",
  450. some_instance="also not valid JSON!",
  451. ),
  452. argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
  453. exit_code=1,
  454. )
  455. self.assertFalse(stdout)
  456. self.assertIn(
  457. "(some_schema)===\n\nTraceback (most recent call last):\n",
  458. stderr,
  459. )
  460. self.assertNotIn("some_instance", stderr)
  461. def test_instance_does_not_exist(self):
  462. self.assertOutputs(
  463. files=dict(some_schema="{}"),
  464. argv=["-i", "nonexisting_instance", "some_schema"],
  465. exit_code=1,
  466. stderr="""\
  467. 'nonexisting_instance' does not exist.
  468. """,
  469. )
  470. def test_instance_does_not_exist_pretty_output(self):
  471. self.assertOutputs(
  472. files=dict(some_schema="{}"),
  473. argv=[
  474. "--output", "pretty",
  475. "-i", "nonexisting_instance",
  476. "some_schema",
  477. ],
  478. exit_code=1,
  479. stderr="""\
  480. ===[FileNotFoundError]===(nonexisting_instance)===
  481. 'nonexisting_instance' does not exist.
  482. -----------------------------
  483. """,
  484. )
  485. def test_schema_does_not_exist(self):
  486. self.assertOutputs(
  487. argv=["nonexisting_schema"],
  488. exit_code=1,
  489. stderr="'nonexisting_schema' does not exist.\n",
  490. )
  491. def test_schema_does_not_exist_pretty_output(self):
  492. self.assertOutputs(
  493. argv=["--output", "pretty", "nonexisting_schema"],
  494. exit_code=1,
  495. stderr="""\
  496. ===[FileNotFoundError]===(nonexisting_schema)===
  497. 'nonexisting_schema' does not exist.
  498. -----------------------------
  499. """,
  500. )
  501. def test_neither_instance_nor_schema_exist(self):
  502. self.assertOutputs(
  503. argv=["-i", "nonexisting_instance", "nonexisting_schema"],
  504. exit_code=1,
  505. stderr="'nonexisting_schema' does not exist.\n",
  506. )
  507. def test_neither_instance_nor_schema_exist_pretty_output(self):
  508. self.assertOutputs(
  509. argv=[
  510. "--output", "pretty",
  511. "-i", "nonexisting_instance",
  512. "nonexisting_schema",
  513. ],
  514. exit_code=1,
  515. stderr="""\
  516. ===[FileNotFoundError]===(nonexisting_schema)===
  517. 'nonexisting_schema' does not exist.
  518. -----------------------------
  519. """,
  520. )
  521. def test_successful_validation(self):
  522. self.assertOutputs(
  523. files=dict(some_schema="{}", some_instance="{}"),
  524. argv=["-i", "some_instance", "some_schema"],
  525. stdout="",
  526. stderr="",
  527. )
  528. def test_successful_validation_pretty_output(self):
  529. self.assertOutputs(
  530. files=dict(some_schema="{}", some_instance="{}"),
  531. argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
  532. stdout="===[SUCCESS]===(some_instance)===\n",
  533. stderr="",
  534. )
  535. def test_successful_validation_of_stdin(self):
  536. self.assertOutputs(
  537. files=dict(some_schema="{}"),
  538. stdin=StringIO("{}"),
  539. argv=["some_schema"],
  540. stdout="",
  541. stderr="",
  542. )
  543. def test_successful_validation_of_stdin_pretty_output(self):
  544. self.assertOutputs(
  545. files=dict(some_schema="{}"),
  546. stdin=StringIO("{}"),
  547. argv=["--output", "pretty", "some_schema"],
  548. stdout="===[SUCCESS]===(<stdin>)===\n",
  549. stderr="",
  550. )
  551. def test_successful_validation_of_just_the_schema(self):
  552. self.assertOutputs(
  553. files=dict(some_schema="{}", some_instance="{}"),
  554. argv=["-i", "some_instance", "some_schema"],
  555. stdout="",
  556. stderr="",
  557. )
  558. def test_successful_validation_of_just_the_schema_pretty_output(self):
  559. self.assertOutputs(
  560. files=dict(some_schema="{}", some_instance="{}"),
  561. argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
  562. stdout="===[SUCCESS]===(some_instance)===\n",
  563. stderr="",
  564. )
  565. def test_successful_validation_via_explicit_base_uri(self):
  566. ref_schema_file = tempfile.NamedTemporaryFile(delete=False)
  567. ref_schema_file.close()
  568. self.addCleanup(os.remove, ref_schema_file.name)
  569. ref_path = Path(ref_schema_file.name)
  570. ref_path.write_text('{"definitions": {"num": {"type": "integer"}}}')
  571. schema = f'{{"$ref": "{ref_path.name}#/definitions/num"}}'
  572. self.assertOutputs(
  573. files=dict(some_schema=schema, some_instance="1"),
  574. argv=[
  575. "-i", "some_instance",
  576. "--base-uri", ref_path.parent.as_uri() + "/",
  577. "some_schema",
  578. ],
  579. stdout="",
  580. stderr="",
  581. )
  582. def test_unsuccessful_validation_via_explicit_base_uri(self):
  583. ref_schema_file = tempfile.NamedTemporaryFile(delete=False)
  584. ref_schema_file.close()
  585. self.addCleanup(os.remove, ref_schema_file.name)
  586. ref_path = Path(ref_schema_file.name)
  587. ref_path.write_text('{"definitions": {"num": {"type": "integer"}}}')
  588. schema = f'{{"$ref": "{ref_path.name}#/definitions/num"}}'
  589. self.assertOutputs(
  590. files=dict(some_schema=schema, some_instance='"1"'),
  591. argv=[
  592. "-i", "some_instance",
  593. "--base-uri", ref_path.parent.as_uri() + "/",
  594. "some_schema",
  595. ],
  596. exit_code=1,
  597. stdout="",
  598. stderr="1: '1' is not of type 'integer'\n",
  599. )
  600. def test_nonexistent_file_with_explicit_base_uri(self):
  601. schema = '{"$ref": "someNonexistentFile.json#definitions/num"}'
  602. instance = "1"
  603. with self.assertRaises(_RefResolutionError) as e:
  604. self.assertOutputs(
  605. files=dict(
  606. some_schema=schema,
  607. some_instance=instance,
  608. ),
  609. argv=[
  610. "-i", "some_instance",
  611. "--base-uri", Path.cwd().as_uri(),
  612. "some_schema",
  613. ],
  614. )
  615. error = str(e.exception)
  616. self.assertIn(f"{os.sep}someNonexistentFile.json'", error)
  617. def test_invalid_explicit_base_uri(self):
  618. schema = '{"$ref": "foo.json#definitions/num"}'
  619. instance = "1"
  620. with self.assertRaises(_RefResolutionError) as e:
  621. self.assertOutputs(
  622. files=dict(
  623. some_schema=schema,
  624. some_instance=instance,
  625. ),
  626. argv=[
  627. "-i", "some_instance",
  628. "--base-uri", "not@UR1",
  629. "some_schema",
  630. ],
  631. )
  632. error = str(e.exception)
  633. self.assertEqual(
  634. error, "unknown url type: 'foo.json'",
  635. )
  636. def test_it_validates_using_the_latest_validator_when_unspecified(self):
  637. # There isn't a better way now I can think of to ensure that the
  638. # latest version was used, given that the call to validator_for
  639. # is hidden inside the CLI, so guard that that's the case, and
  640. # this test will have to be updated when versions change until
  641. # we can think of a better way to ensure this behavior.
  642. self.assertIs(Draft202012Validator, _LATEST_VERSION)
  643. self.assertOutputs(
  644. files=dict(some_schema='{"const": "check"}', some_instance='"a"'),
  645. argv=["-i", "some_instance", "some_schema"],
  646. exit_code=1,
  647. stdout="",
  648. stderr="a: 'check' was expected\n",
  649. )
  650. def test_it_validates_using_draft7_when_specified(self):
  651. """
  652. Specifically, `const` validation applies for Draft 7.
  653. """
  654. schema = """
  655. {
  656. "$schema": "http://json-schema.org/draft-07/schema#",
  657. "const": "check"
  658. }
  659. """
  660. instance = '"foo"'
  661. self.assertOutputs(
  662. files=dict(some_schema=schema, some_instance=instance),
  663. argv=["-i", "some_instance", "some_schema"],
  664. exit_code=1,
  665. stdout="",
  666. stderr="foo: 'check' was expected\n",
  667. )
  668. def test_it_validates_using_draft4_when_specified(self):
  669. """
  670. Specifically, `const` validation *does not* apply for Draft 4.
  671. """
  672. schema = """
  673. {
  674. "$schema": "http://json-schema.org/draft-04/schema#",
  675. "const": "check"
  676. }
  677. """
  678. instance = '"foo"'
  679. self.assertOutputs(
  680. files=dict(some_schema=schema, some_instance=instance),
  681. argv=["-i", "some_instance", "some_schema"],
  682. stdout="",
  683. stderr="",
  684. )
  685. class TestParser(TestCase):
  686. FakeValidator = fake_validator()
  687. def test_find_validator_by_fully_qualified_object_name(self):
  688. arguments = cli.parse_args(
  689. [
  690. "--validator",
  691. "jsonschema.tests.test_cli.TestParser.FakeValidator",
  692. "--instance", "mem://some/instance",
  693. "mem://some/schema",
  694. ],
  695. )
  696. self.assertIs(arguments["validator"], self.FakeValidator)
  697. def test_find_validator_in_jsonschema(self):
  698. arguments = cli.parse_args(
  699. [
  700. "--validator", "Draft4Validator",
  701. "--instance", "mem://some/instance",
  702. "mem://some/schema",
  703. ],
  704. )
  705. self.assertIs(arguments["validator"], Draft4Validator)
  706. def cli_output_for(self, *argv):
  707. stdout, stderr = StringIO(), StringIO()
  708. with redirect_stdout(stdout), redirect_stderr(stderr): # noqa: SIM117
  709. with self.assertRaises(SystemExit):
  710. cli.parse_args(argv)
  711. return stdout.getvalue(), stderr.getvalue()
  712. def test_unknown_output(self):
  713. stdout, stderr = self.cli_output_for(
  714. "--output", "foo",
  715. "mem://some/schema",
  716. )
  717. self.assertIn("invalid choice: 'foo'", stderr)
  718. self.assertFalse(stdout)
  719. def test_useless_error_format(self):
  720. stdout, stderr = self.cli_output_for(
  721. "--output", "pretty",
  722. "--error-format", "foo",
  723. "mem://some/schema",
  724. )
  725. self.assertIn(
  726. "--error-format can only be used with --output plain",
  727. stderr,
  728. )
  729. self.assertFalse(stdout)
  730. class TestCLIIntegration(TestCase):
  731. def test_license(self):
  732. output = subprocess.check_output(
  733. [sys.executable, "-m", "pip", "show", "jsonschema"],
  734. stderr=subprocess.STDOUT,
  735. )
  736. self.assertIn(b"License: MIT", output)
  737. def test_version(self):
  738. version = subprocess.check_output(
  739. [sys.executable, "-W", "ignore", "-m", "jsonschema", "--version"],
  740. stderr=subprocess.STDOUT,
  741. )
  742. version = version.decode("utf-8").strip()
  743. self.assertEqual(version, metadata.version("jsonschema"))
  744. def test_no_arguments_shows_usage_notes(self):
  745. output = subprocess.check_output(
  746. [sys.executable, "-m", "jsonschema"],
  747. stderr=subprocess.STDOUT,
  748. )
  749. output_for_help = subprocess.check_output(
  750. [sys.executable, "-m", "jsonschema", "--help"],
  751. stderr=subprocess.STDOUT,
  752. )
  753. self.assertEqual(output, output_for_help)