Skip to content

Commit 269301a

Browse files
DylanBartelsdylan bartelsgadomski
authored
feat: add the pystapi-validator (#8)
Ran with: ``` uv run stapi-validator ``` Will need a local instance of stapi fastapi to run for the tests to pass. --------- Co-authored-by: dylan bartels <[email protected]> Co-authored-by: Pete Gadomski <[email protected]>
1 parent 0dec10f commit 269301a

File tree

7 files changed

+707
-5
lines changed

7 files changed

+707
-5
lines changed

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ description = "Monorepo for Satellite Tasking API (STAPI) Specification Python p
55
readme = "README.md"
66
requires-python = ">=3.10"
77
dependencies = [
8+
"pystapi-validator",
89
"stapi-pydantic",
9-
"stapi-fastapi",
10+
"stapi-fastapi"
1011
]
1112

1213
[dependency-groups]
@@ -30,9 +31,10 @@ docs = [
3031
default-groups = ["dev", "docs"]
3132

3233
[tool.uv.workspace]
33-
members = ["stapi-pydantic", "stapi-fastapi"]
34+
members = ["pystapi-validator", "stapi-pydantic", "stapi-fastapi"]
3435

3536
[tool.uv.sources]
37+
pystapi-validator.workspace = true
3638
stapi-pydantic.workspace = true
3739
stapi-fastapi.workspace = true
3840

@@ -58,6 +60,7 @@ max-complexity = 8 # default 10
5860
[tool.mypy]
5961
strict = true
6062
files = [
63+
"pystapi-validator/src/pystapi_validator/**/*.py",
6164
"stapi-pydantic/src/stapi_pydantic/**/*.py",
6265
"stapi-fastapi/src/stapi_fastapi/**/*.py"
6366
]

pystapi-validator/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# pystapi-validator
2+
3+
This project provides API validation for STAPI FastAPI implementations using Schemathesis against the latest STAPI OpenAPI
4+
specification.
5+
6+
## Configuration
7+
8+
- The STAPI OpenAPI specification is fetched from:
9+
[STAPI OpenAPI Spec](https://raw.githubusercontent.com/stapi-spec/stapi-spec/refs/heads/main/openapi.yaml)
10+
- The base URL for the API being tested is set to `http://localhost:8000`. Update the `BASE_URL` in `tests/validate_api.py`
11+
if your API is hosted elsewhere.
12+
13+
## Setup
14+
15+
1. Install dependencies:
16+
17+
```bash
18+
uv sync
19+
```
20+
21+
1. Run tests and generate report:
22+
23+
```bash
24+
uv run pytest tests/validate_api.py --html=report.html --self-contained-html
25+
```
26+
27+
1. Open `report.html` in your browser to view the detailed test report.

pystapi-validator/pyproject.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[project]
2+
name = "pystapi-validator"
3+
version = "0.1.0"
4+
description = "API validation for STAPI FastAPI using Schemathesis and OpenAPI YAML with reporting"
5+
authors = [
6+
{ name = "Brian Terry", email = "[email protected]" }
7+
]
8+
license = "MIT"
9+
readme = "README.md"
10+
requires-python = ">=3.10"
11+
dependencies = [
12+
"schemathesis>=3.37.0",
13+
"pytest>=8.3.3",
14+
"requests>=2.32.3",
15+
"pyyaml>=6.0.2",
16+
"pytest-html>=4.1.1",
17+
"pytest-metadata>=3.1.1",
18+
]
19+
20+
[project.scripts]
21+
pystapi-validator = "pystapi_validator:main"
22+
23+
[build-system]
24+
requires = ["hatchling"]
25+
build-backend = "hatchling.build"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import sys
2+
3+
import pytest
4+
5+
6+
def main() -> None:
7+
sys.exit(pytest.main(["pystapi-validator/tests/validate_api.py"]))
8+
9+
10+
if __name__ == "__main__":
11+
main()

pystapi-validator/tests/__init__.py

Whitespace-only changes.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import json
2+
3+
import pytest
4+
import schemathesis
5+
from schemathesis.checks import (
6+
content_type_conformance,
7+
negative_data_rejection,
8+
not_a_server_error,
9+
response_headers_conformance,
10+
response_schema_conformance,
11+
status_code_conformance,
12+
)
13+
14+
schemathesis.experimental.OPEN_API_3_1.enable()
15+
16+
SCHEMA_URL = "https://raw.githubusercontent.com/stapi-spec/stapi-spec/refs/heads/main/openapi.yaml"
17+
schema = schemathesis.from_uri(SCHEMA_URL)
18+
19+
BASE_URL = "http://localhost:8000"
20+
21+
22+
@schema.parametrize()
23+
def test_api(case):
24+
response = case.call_and_validate(base_url=BASE_URL)
25+
case.validate_response(response)
26+
27+
not_a_server_error(response, case)
28+
status_code_conformance(response, case)
29+
content_type_conformance(response, case)
30+
response_schema_conformance(response, case)
31+
response_headers_conformance(response, case)
32+
negative_data_rejection(response, case)
33+
34+
35+
def test_openapi_specification():
36+
assert schema.validate()
37+
38+
39+
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
40+
def pytest_runtest_makereport(item, call):
41+
outcome = yield
42+
rep = outcome.get_result()
43+
if rep.when == "call":
44+
item.session.results = getattr(item.session, "results", {})
45+
item.session.results[item.nodeid] = rep
46+
47+
48+
def pytest_sessionfinish(session, exitstatus):
49+
if hasattr(session, "results"):
50+
with open("test_results.json", "w") as f:
51+
json.dump(
52+
{
53+
nodeid: {
54+
"outcome": rep.outcome,
55+
"longrepr": str(rep.longrepr) if rep.longrepr else None,
56+
}
57+
for nodeid, rep in session.results.items()
58+
},
59+
f,
60+
indent=2,
61+
)

0 commit comments

Comments
 (0)