Skip to content

Commit 4a0fdca

Browse files
committed
Add tests for custom validator usage
Using the mock_module fixture (lifted to top-level conftest), setup a custom validator which does some "real" validation work.
1 parent 58f2848 commit 4a0fdca

File tree

3 files changed

+180
-32
lines changed

3 files changed

+180
-32
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import json
2+
3+
import pytest
4+
5+
# define a calendar event schema and then use a custom validator to validate that there
6+
# are no events with "Occult" in their names
7+
SCHEMA = {
8+
"$schema": "http://json-schema.org/draft-07/schema",
9+
"definitions": {
10+
"calendar-event": {
11+
"type": "object",
12+
"properties": {
13+
"title": {"type": "string"},
14+
"start": {"type": "string", "format": "date-time"},
15+
"end": {"type": "string", "format": "date-time"},
16+
},
17+
}
18+
},
19+
"properties": {
20+
"events": {
21+
"type": "array",
22+
"items": {"$ref": "#/definitions/calendar-event"},
23+
},
24+
},
25+
"required": ["events"],
26+
}
27+
VALID_DOC = {
28+
"events": [
29+
{
30+
"title": "Weekly Production Meeting",
31+
"start": "2019-06-24T09:00:00-05:00",
32+
"end": "2019-06-24T10:00:00-05:00",
33+
},
34+
{
35+
"title": "Catch Up",
36+
"start": "2019-06-24T10:00:00-05:00",
37+
"end": "2019-06-24T10:30:00-05:00",
38+
},
39+
]
40+
}
41+
INVALID_DOC = {
42+
"events": [
43+
{
44+
"title": "Weekly Production Meeting",
45+
"start": "2019-06-24T09:00:00-05:00",
46+
"end": "2019-06-24T10:00:00-05:00",
47+
},
48+
{
49+
"title": "Catch Up",
50+
"start": "2019-06-24T10:00:00-05:00",
51+
"end": "2019-06-24T10:30:00-05:00",
52+
},
53+
{
54+
"title": "Occult Study Session",
55+
"start": "2019-06-24T10:00:00-05:00",
56+
"end": "2019-06-24T12:00:00-05:00",
57+
},
58+
]
59+
}
60+
61+
62+
@pytest.fixture(autouse=True)
63+
def _foo_module(mock_module):
64+
mock_module(
65+
"foo.py",
66+
"""\
67+
import jsonschema
68+
69+
class MyValidator:
70+
def __init__(self, schema, *args, **kwargs):
71+
self.schema = schema
72+
self.real_validator = jsonschema.validators.Draft7Validator(
73+
schema, *args, **kwargs
74+
)
75+
76+
def iter_errors(self, data, *args, **kwargs):
77+
yield from self.real_validator.iter_errors(data, *args, **kwargs)
78+
for event in data["events"]:
79+
if "Occult" in event["title"]:
80+
yield jsonschema.exceptions.ValidationError(
81+
"Error! Occult event detected! Run!",
82+
validator=None,
83+
validator_value=None,
84+
instance=event,
85+
schema=self.schema,
86+
)
87+
""",
88+
)
89+
90+
91+
def test_custom_validator_class_can_detect_custom_conditions(run_line, tmp_path):
92+
doc = tmp_path / "invalid.json"
93+
doc.write_text(json.dumps(INVALID_DOC))
94+
95+
schema = tmp_path / "schema.json"
96+
schema.write_text(json.dumps(SCHEMA))
97+
98+
result = run_line(
99+
[
100+
"check-jsonschema",
101+
"--schemafile",
102+
str(schema),
103+
str(doc),
104+
]
105+
)
106+
assert result.exit_code == 0, result.stdout # pass
107+
108+
result = run_line(
109+
[
110+
"check-jsonschema",
111+
"--schemafile",
112+
str(schema),
113+
"--validator-class",
114+
"foo:MyValidator",
115+
str(doc),
116+
],
117+
)
118+
assert result.exit_code == 1 # fail
119+
assert "Occult event detected" in result.stdout, result.stdout
120+
121+
122+
def test_custom_validator_class_can_pass_when_valid(run_line, tmp_path):
123+
doc = tmp_path / "valid.json"
124+
doc.write_text(json.dumps(VALID_DOC))
125+
126+
schema = tmp_path / "schema.json"
127+
schema.write_text(json.dumps(SCHEMA))
128+
129+
result = run_line(
130+
[
131+
"check-jsonschema",
132+
"--schemafile",
133+
str(schema),
134+
str(doc),
135+
]
136+
)
137+
assert result.exit_code == 0, result.stdout # pass
138+
139+
result = run_line(
140+
[
141+
"check-jsonschema",
142+
"--schemafile",
143+
str(schema),
144+
"--validator-class",
145+
"foo:MyValidator",
146+
str(doc),
147+
],
148+
)
149+
assert result.exit_code == 0 # pass

tests/conftest.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import pathlib
2+
import sys
3+
14
import pytest
25
import responses
36

@@ -8,3 +11,30 @@ def mocked_responses():
811
yield
912
responses.stop()
1013
responses.reset()
14+
15+
16+
@pytest.fixture
17+
def mock_module(tmp_path, monkeypatch):
18+
monkeypatch.syspath_prepend(tmp_path)
19+
all_names_to_clear = []
20+
21+
def func(path, text):
22+
path = pathlib.Path(path)
23+
mod_dir = tmp_path / (path.parent)
24+
mod_dir.mkdir(parents=True, exist_ok=True)
25+
for part in path.parts[:-1]:
26+
(tmp_path / part / "__init__.py").touch()
27+
28+
(tmp_path / path).write_text(text)
29+
30+
for i in range(len(path.parts)):
31+
modname = ".".join(path.parts[: i + 1])
32+
if modname.endswith(".py"):
33+
modname = modname[:-3]
34+
all_names_to_clear.append(modname)
35+
36+
yield func
37+
38+
for name in all_names_to_clear:
39+
if name in sys.modules:
40+
del sys.modules[name]

tests/unit/test_cli_parse.py

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from __future__ import annotations
22

3-
import pathlib
4-
import sys
53
from unittest import mock
64

75
import click
@@ -29,33 +27,6 @@ def mock_parse_result():
2927
yield args
3028

3129

32-
@pytest.fixture
33-
def mock_module(tmp_path, monkeypatch):
34-
monkeypatch.syspath_prepend(tmp_path)
35-
all_names_to_clear = []
36-
37-
def func(path, text):
38-
path = pathlib.Path(path)
39-
mod_dir = tmp_path / (path.parent)
40-
mod_dir.mkdir(parents=True, exist_ok=True)
41-
for part in path.parts[:-1]:
42-
(tmp_path / part / "__init__.py").touch()
43-
44-
(tmp_path / path).write_text(text)
45-
46-
for i in range(len(path.parts)):
47-
modname = ".".join(path.parts[: i + 1])
48-
if modname.endswith(".py"):
49-
modname = modname[:-3]
50-
all_names_to_clear.append(modname)
51-
52-
yield func
53-
54-
for name in all_names_to_clear:
55-
if name in sys.modules:
56-
del sys.modules[name]
57-
58-
5930
@pytest.fixture(autouse=True)
6031
def mock_cli_exec(boxed_context):
6132
def get_ctx(*args):
@@ -310,9 +281,7 @@ def test_can_specify_custom_validator_class(runner, mock_parse_result, mock_modu
310281
@pytest.mark.parametrize(
311282
"failmode", ("syntax", "import", "attr", "function", "non_callable")
312283
)
313-
def test_can_custom_validator_class_fails(
314-
runner, mock_parse_result, mock_module, failmode
315-
):
284+
def test_custom_validator_class_fails(runner, mock_parse_result, mock_module, failmode):
316285
mock_module(
317286
"foo.py",
318287
"""\

0 commit comments

Comments
 (0)