litellm/tests/unit/test_check_openapi_docs.py
Yuneng Jiang e8354313aa
feat(proxy): add POST /key/revoke so anyone who finds a published key can turn it off
Anyone who finds an accidentally published virtual key can now turn it off without an admin. The route takes one key or up to 100, blocks each live one and drops it from every worker's cache, and answers every valid request with the same empty 202. Each source address gets 60 requests an hour, counted in Redis when it is configured so all workers share one count

The OpenAPI docs gate now checks the schema of a route's lowest 2xx response instead of only 200, so a route whose success code is 202 can pass it
2026-10-31 18:15:28 -07:00

74 lines
3.2 KiB
Python

"""Tests for scripts/check_openapi_docs.py, the scan behind scripts/openapi_docs_gate.py."""
from typing import Final
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}
def test_a_fully_documented_post_has_no_findings() -> None:
assert checker.violations({"paths": {"/v1/thing": {"post": _DOCUMENTED}}}) == ()
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_the_schema_is_judged_on_the_lowest_2xx_response() -> None:
accepted: Final = {**_DOCUMENTED, "responses": {"202": _RESPONSES["200"], "422": {"description": "Invalid"}}}
untyped_202: Final = {**_DOCUMENTED, "responses": {"202": {"description": "Accepted"}, "422": _RESPONSES["200"]}}
untyped_200: Final = {**_DOCUMENTED, "responses": {"200": {"description": "OK"}, "201": _RESPONSES["200"]}}
no_2xx: Final = {**_DOCUMENTED, "responses": {"422": _RESPONSES["200"]}}
spec: Final = {
"paths": {
"/a": {"post": accepted},
"/b": {"post": untyped_202},
"/c": {"post": untyped_200},
"/d": {"post": no_2xx},
}
}
assert checker.violations(spec) == (
checker.Violation("POST", "/b", "OA003"),
checker.Violation("POST", "/c", "OA003"),
checker.Violation("POST", "/d", "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"]