Skip to content

Commit ea7ebc0

Browse files
Copilotp1c2u
andcommitted
Integrate openapi-schema-test-suite as external test suite
Co-authored-by: p1c2u <1679024+p1c2u@users.noreply.github.com>
1 parent ffde5df commit ea7ebc0

File tree

5 files changed

+166
-4
lines changed

5 files changed

+166
-4
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "vendor/openapi-schema-test-suite"]
2+
path = vendor/openapi-schema-test-suite
3+
url = https://github.com/python-openapi/openapi-schema-test-suite.git

openapi_schema_validator/validators.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,9 @@ def _build_oas31_validator() -> Any:
115115
Draft202012Validator,
116116
{
117117
# adjusted to OAS
118-
"allOf": oas_keywords.allOf,
119-
"oneOf": oas_keywords.oneOf,
120-
"anyOf": oas_keywords.anyOf,
121118
"pattern": oas_keywords.pattern,
122119
"description": oas_keywords.not_implemented,
123-
# fixed OAS fields
120+
# fixed OAS fields - discriminator is annotation-only in OAS 3.1+
124121
"discriminator": oas_keywords.not_implemented,
125122
"xml": oas_keywords.not_implemented,
126123
"externalDocs": oas_keywords.not_implemented,

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ classifiers = [
8080
]
8181
include = [
8282
{path = "tests", format = "sdist"},
83+
{path = "vendor/openapi-schema-test-suite/tests", format = "sdist"},
8384
]
8485

8586
[tool.poetry.dependencies]

tests/test_suite.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
Test runner for the openapi-schema-test-suite.
3+
4+
This module integrates the external test suite from
5+
https://github.com/python-openapi/openapi-schema-test-suite
6+
to validate OAS 3.1 and OAS 3.2 schema validators against
7+
the canonical test cases.
8+
"""
9+
import json
10+
from pathlib import Path
11+
from typing import Any
12+
13+
import pytest
14+
from jsonschema.exceptions import ValidationError
15+
16+
from openapi_schema_validator import OAS31Validator
17+
from openapi_schema_validator import OAS32Validator
18+
from openapi_schema_validator import oas31_format_checker
19+
from openapi_schema_validator import oas32_format_checker
20+
21+
SUITE_ROOT = (
22+
Path(__file__).parent.parent
23+
/ "vendor"
24+
/ "openapi-schema-test-suite"
25+
/ "tests"
26+
)
27+
28+
# Known failures due to limitations in the underlying jsonschema library.
29+
# Each entry is (dialect, relative_path, case_description, test_description).
30+
_KNOWN_FAILURES: dict[tuple[str, str, str, str], str] = {
31+
(
32+
"oas31",
33+
"unevaluated.json",
34+
"unevaluatedProperties with if/then/else",
35+
"non-premium type with name is valid",
36+
): (
37+
"jsonschema does not collect annotations from failing 'if' subschemas, "
38+
"causing properties evaluated by 'if' to be reported as unevaluated"
39+
),
40+
(
41+
"oas32",
42+
"unevaluated.json",
43+
"unevaluatedProperties with if/then/else",
44+
"non-premium type with name is valid",
45+
): (
46+
"jsonschema does not collect annotations from failing 'if' subschemas, "
47+
"causing properties evaluated by 'if' to be reported as unevaluated"
48+
),
49+
(
50+
"oas31",
51+
"optional/format/format-assertion.json",
52+
"format uri with assertion",
53+
"a relative URI is not a valid URI",
54+
): "uri format checker does not validate RFC 3986 absolute-URI requirement",
55+
(
56+
"oas31",
57+
"optional/format/format-assertion.json",
58+
"format uri with assertion",
59+
"an invalid URI is not valid",
60+
): "uri format checker does not validate RFC 3986 absolute-URI requirement",
61+
(
62+
"oas32",
63+
"optional/format/format-assertion.json",
64+
"format uri with assertion",
65+
"a relative URI is not a valid URI",
66+
): "uri format checker does not validate RFC 3986 absolute-URI requirement",
67+
(
68+
"oas32",
69+
"optional/format/format-assertion.json",
70+
"format uri with assertion",
71+
"an invalid URI is not valid",
72+
): "uri format checker does not validate RFC 3986 absolute-URI requirement",
73+
}
74+
75+
_DIALECT_CONFIG: dict[str, dict[str, Any]] = {
76+
"oas31": {
77+
"validator_class": OAS31Validator,
78+
"format_checker": oas31_format_checker,
79+
},
80+
"oas32": {
81+
"validator_class": OAS32Validator,
82+
"format_checker": oas32_format_checker,
83+
},
84+
}
85+
86+
87+
def _collect_params() -> list[pytest.param]:
88+
params: list[pytest.param] = []
89+
90+
for dialect, config in _DIALECT_CONFIG.items():
91+
dialect_dir = SUITE_ROOT / dialect
92+
if not dialect_dir.is_dir():
93+
continue
94+
95+
for json_path in sorted(dialect_dir.rglob("*.json")):
96+
rel_path = json_path.relative_to(dialect_dir)
97+
is_in_optional_dir = rel_path.parts[0] == "optional"
98+
format_checker = config["format_checker"] if is_in_optional_dir else None
99+
100+
test_cases: list[dict[str, Any]] = json.loads(
101+
json_path.read_text(encoding="utf-8")
102+
)
103+
for case in test_cases:
104+
case_desc: str = case["description"]
105+
schema: dict[str, Any] = case["schema"]
106+
for test in case["tests"]:
107+
test_desc: str = test["description"]
108+
data: Any = test["data"]
109+
expected_valid: bool = test["valid"]
110+
111+
param_id = (
112+
f"{dialect}/{rel_path}/{case_desc}/{test_desc}"
113+
)
114+
failure_key = (
115+
dialect,
116+
str(rel_path),
117+
case_desc,
118+
test_desc,
119+
)
120+
marks: list[pytest.MarkDecorator] = []
121+
if failure_key in _KNOWN_FAILURES:
122+
marks.append(
123+
pytest.mark.xfail(
124+
reason=_KNOWN_FAILURES[failure_key],
125+
strict=True,
126+
)
127+
)
128+
params.append(
129+
pytest.param(
130+
config["validator_class"],
131+
schema,
132+
format_checker,
133+
data,
134+
expected_valid,
135+
id=param_id,
136+
marks=marks,
137+
)
138+
)
139+
140+
return params
141+
142+
143+
@pytest.mark.parametrize(
144+
"validator_class,schema,format_checker,data,expected_valid",
145+
_collect_params(),
146+
)
147+
def test_suite(
148+
validator_class: Any,
149+
schema: dict[str, Any],
150+
format_checker: Any,
151+
data: Any,
152+
expected_valid: bool,
153+
) -> None:
154+
validator = validator_class(schema, format_checker=format_checker)
155+
errors = list(validator.iter_errors(data))
156+
is_valid = len(errors) == 0
157+
assert is_valid == expected_valid, (
158+
f"Expected valid={expected_valid}, got valid={is_valid}. "
159+
f"Errors: {[e.message for e in errors]}"
160+
)

vendor/openapi-schema-test-suite

0 commit comments

Comments
 (0)