1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
"""Test Python JSONPath against the JSONPath Compliance Test Suite.
The CTS is a submodule located in /tests/cts. After a git clone, run
`git submodule update --init` from the root of the repository.
"""
import asyncio
import json
import operator
from typing import List
import pytest
from jsonpath import JSONPathEnvironment
from jsonpath import JSONPathError
from jsonpath import NodeList
from ._cts_case import Case
# CTS tests that are expected to fail when JSONPathEnvironment.strict is False.
XFAIL_INVALID = {
"basic, no leading whitespace",
"basic, no trailing whitespace",
"basic, root node identifier in brackets without filter selector",
"filter, equals number, invalid 00",
"filter, equals number, invalid leading 0",
"filter, true, incorrectly capitalized",
"filter, false, incorrectly capitalized",
"filter, null, incorrectly capitalized",
"name selector, double quotes, single high surrogate",
"name selector, double quotes, single low surrogate",
"name selector, double quotes, high high surrogate",
"name selector, double quotes, low low surrogate",
"name selector, double quotes, surrogate non-surrogate",
"name selector, double quotes, non-surrogate surrogate",
"name selector, double quotes, surrogate supplementary",
"name selector, double quotes, supplementary surrogate",
}
XFAIL_VALID = {
"filter, index segment on object, selects nothing",
}
# CTS test that will only pass if the third party `regex` package is installed.
REGEX_ONLY = {
"functions, match, dot matcher on \\u2028",
"functions, match, dot matcher on \\u2029",
"functions, search, dot matcher on \\u2028",
"functions, search, dot matcher on \\u2029",
"functions, match, filter, match function, unicode char class, uppercase",
"functions, match, filter, match function, unicode char class negated, uppercase",
"functions, search, filter, search function, unicode char class, uppercase",
"functions, search, filter, search function, unicode char class negated, uppercase",
}
with open("tests/cts/cts.json", encoding="utf8") as fd:
data = json.load(fd)
CASES = [Case(**case) for case in data["tests"]]
def valid_cases() -> List[Case]:
return [case for case in CASES if not case.invalid_selector]
def invalid_cases() -> List[Case]:
return [case for case in CASES if case.invalid_selector]
@pytest.fixture()
def env() -> JSONPathEnvironment:
return JSONPathEnvironment(strict=True)
@pytest.mark.parametrize("case", valid_cases(), ids=operator.attrgetter("name"))
def test_compliance_strict(env: JSONPathEnvironment, case: Case) -> None:
if not env.regex_available and case.name in REGEX_ONLY:
pytest.skip(reason="requires regex package")
assert case.document is not None
nodes = NodeList(env.finditer(case.selector, case.document))
case.assert_nodes(nodes)
@pytest.mark.parametrize("case", valid_cases(), ids=operator.attrgetter("name"))
def test_compliance_async_strict(env: JSONPathEnvironment, case: Case) -> None:
if not env.regex_available and case.name in REGEX_ONLY:
pytest.skip(reason="requires regex package")
async def coro() -> NodeList:
assert case.document is not None
it = await env.finditer_async(case.selector, case.document)
return NodeList([node async for node in it])
nodes = asyncio.run(coro())
case.assert_nodes(nodes)
@pytest.mark.parametrize("case", invalid_cases(), ids=operator.attrgetter("name"))
def test_invalid_selectors_strict(env: JSONPathEnvironment, case: Case) -> None:
with pytest.raises(JSONPathError):
env.compile(case.selector)
@pytest.mark.parametrize("case", valid_cases(), ids=operator.attrgetter("name"))
def test_compliance_lax(case: Case) -> None:
env = JSONPathEnvironment(strict=False)
if not env.regex_available and case.name in REGEX_ONLY:
pytest.skip(reason="requires regex package")
assert case.document is not None
nodes = NodeList(env.finditer(case.selector, case.document))
if case.results is not None:
assert case.results_paths is not None
if case.name in XFAIL_VALID:
assert nodes.values() not in case.results
assert nodes.paths() in case.results_paths
else:
assert nodes.values() in case.results
assert nodes.paths() in case.results_paths
else:
assert case.result_paths is not None
if case.name in XFAIL_VALID:
assert nodes.values() != case.result
assert nodes.paths() != case.result_paths
else:
assert nodes.values() == case.result
assert nodes.paths() == case.result_paths
@pytest.mark.parametrize("case", invalid_cases(), ids=operator.attrgetter("name"))
def test_invalid_selectors_lax(case: Case) -> None:
env = JSONPathEnvironment(strict=False)
if case.name in XFAIL_INVALID:
env.compile(case.selector)
else:
with pytest.raises(JSONPathError):
env.compile(case.selector)
|