litellm/tests/unit/test_check_openapi_docs.py

65 lines
2.8 KiB
Python

"""Tests for scripts/check_openapi_docs.py, the scan behind scripts/openapi_docs_gate.py."""
from typing import Final
import pytest
import check_openapi_docs as checker
_BODY: Final = {"content": {"application/json": {"schema": {"type": "object"}}}}
_RESPONSES: Final = {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Out"}}}}}
_DOCUMENTED: Final = {"description": "Does a thing", "requestBody": _BODY, "responses": _RESPONSES}
@pytest.mark.parametrize(
("request_media_type", "response_media_type"),
(("application/json", "application/json"), ("application/sdp", "application/sdp"), ("multipart/form-data", "audio/wav")),
)
def test_a_fully_documented_post_has_no_findings(request_media_type: str, response_media_type: str) -> None:
operation: Final = {
**_DOCUMENTED,
"requestBody": {"content": {request_media_type: {"schema": {"type": "string"}}}},
"responses": {"200": {"content": {response_media_type: {"schema": {"type": "string"}}}}},
}
assert checker.violations({"paths": {"/v1/thing": {"post": operation}}}) == ()
def test_each_missing_piece_is_its_own_rule() -> None:
bare: Final = {"summary": "Thing", "responses": {"200": {"description": "Successful Response"}}}
codes: Final = {v.code for v in checker.violations({"paths": {"/v1/thing": {"post": bare}}})}
assert codes == {"OA001", "OA002", "OA003"}
def test_only_the_missing_piece_is_reported() -> None:
no_body: Final = {**_DOCUMENTED, "requestBody": {}}
blank_description: Final = {**_DOCUMENTED, "description": " "}
untyped_200: Final = {**_DOCUMENTED, "responses": {"200": {"content": {"application/json": {"schema": {}}}}}}
spec: Final = {
"paths": {
"/a": {"post": no_body},
"/b": {"post": blank_description},
"/c": {"post": untyped_200},
}
}
assert checker.violations(spec) == (
checker.Violation("POST", "/a", "OA001"),
checker.Violation("POST", "/b", "OA002"),
checker.Violation("POST", "/c", "OA003"),
)
def test_non_post_operations_are_not_gated() -> None:
spec: Final = {"paths": {"/v1/thing": {"get": {}, "delete": {}, "put": {}, "post": _DOCUMENTED}}}
assert checker.violations(spec) == ()
def test_findings_are_sorted_by_path_so_counts_are_stable() -> None:
spec: Final = {"paths": {"/z": {"post": {}}, "/a": {"post": {}}}}
assert [v.path for v in checker.violations(spec)] == ["/a", "/a", "/a", "/z", "/z", "/z"]
def test_main_reads_a_spec_file_and_prints_one_line_per_finding(tmp_path, capsys) -> None:
spec_file: Final = tmp_path / "openapi.json"
spec_file.write_text('{"paths": {"/v1/thing": {"post": {"description": "x", "requestBody": {}}}}}')
assert checker.main([str(spec_file)]) == 0
assert capsys.readouterr().out.splitlines() == ["POST /v1/thing OA001", "POST /v1/thing OA003"]