Skip to content

Commit 0ebdfee

Browse files
authored
Merge pull request #1 from BarnabasG/initial
v1.0.0
2 parents 2cf2b81 + 4892318 commit 0ebdfee

30 files changed

+4381
-2
lines changed

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
__pycache__/
2+
.pytest_cache/
3+
.venv/
4+
reports/
5+
*.egg-info/
6+
dist/
7+
.history/
8+
.lh/
9+
.vscode/
10+
.ruff_cache/
11+
htmlcov/
12+
.python-version
13+
.coverage
14+
coverage.*
15+
*pypi_token*

Makefile

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Makefile
2+
3+
.PHONY: ruff mypy test clean clean-all
4+
5+
PYPI_TOKEN := $(shell type .pypi_token 2>nul || echo "")
6+
TEST_PYPI_TOKEN := $(shell type .test_pypi_token 2>nul || echo "")
7+
8+
ruff:
9+
@echo "Running ruff..."
10+
@uv run ruff format .
11+
@uv run ruff check .
12+
13+
mypy:
14+
@echo "Running mypy..."
15+
@uv run mypy
16+
17+
format: ruff mypy
18+
19+
test:
20+
@echo "Running plugin tests..."
21+
@uv run python -u -m pytest tests/
22+
23+
test-example:
24+
@echo "Running example tests with API coverage..."
25+
@uv run python -u -m pytest example/tests/ --api-cov-report --api-cov-fail-under=50
26+
27+
test-example-parallel:
28+
@echo "Running example tests with API coverage in parallel..."
29+
@uv run python -u -m pytest example/tests/ --api-cov-report --api-cov-fail-under=50 -n 3
30+
31+
cover:
32+
@echo "Running tests with code coverage..."
33+
@uv run python -u -m pytest tests/ --cov=src --cov-report=term-missing --cov-report=html --cov-report=xml
34+
35+
clean:
36+
@echo "Cleaning up build artifacts..."
37+
@if exist build rmdir /s /q build
38+
@if exist dist rmdir /s /q dist
39+
@if exist .venv rmdir /s /q .venv
40+
@if exist *.egg-info rmdir /s /q *.egg-info
41+
@if exist .coverage del .coverage
42+
@if exist htmlcov rmdir /s /q htmlcov
43+
@if exist coverage.xml del coverage.xml
44+
@if exist .pytest_cache rmdir /s /q .pytest_cache
45+
@if exist __pycache__ rmdir /s /q __pycache__
46+
47+
build:
48+
@echo "Building plugin..."
49+
@uv sync
50+
@uv build
51+
52+
publish:
53+
@echo "Publishing plugin..."
54+
@uv publish --token $(PYPI_TOKEN)
55+
56+
publish-test:
57+
@echo "Publishing plugin to test PyPI..."
58+
@echo //$(TEST_PYPI_TOKEN)//
59+
@uv publish --token $(TEST_PYPI_TOKEN) --index testpypi
60+
61+
verify-publish:
62+
@echo "Verifying plugin was published to PyPI..."
63+
@uv run --with pytest-api-cov --no-project -- python -c "import pytest_api_cov; print(f'Plugin verified successfully. Version: {pytest_api_cov.__version__}')"

README.md

Lines changed: 313 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,313 @@
1-
# api-coverage
2-
Pytest plugin to capture coverage of api endpoints
1+
# pytest-api-cov
2+
3+
A **pytest plugin** that measures **API endpoint coverage** for FastAPI and Flask applications. Know which endpoints are tested and which are missing coverage.
4+
5+
## Features
6+
7+
- **Zero Configuration**: Plug-and-play with Flask/FastAPI apps - just install and run
8+
- **Terminal Reports**: Rich terminal output with detailed coverage information
9+
- **JSON Reports**: Export coverage data for CI/CD integration
10+
- **Setup Wizard**: Interactive setup wizard for complex projects
11+
12+
## Quick Start
13+
14+
### Installation
15+
16+
```bash
17+
pip install pytest-api-cov
18+
```
19+
20+
### Basic Usage
21+
22+
For most projects, no configuration is needed:
23+
24+
```bash
25+
# Just add the flag to your pytest command
26+
pytest --api-cov-report
27+
```
28+
29+
The plugin will automatically discover your Flask/FastAPI app if it's in common locations:
30+
- `app.py` (with variable `app`, `application`, or `main`)
31+
- `main.py` (with variable `app`, `application`, or `main`)
32+
- `server.py` (with variable `app`, `application`, or `server`)
33+
34+
### Example
35+
36+
Given this FastAPI app in `app.py`:
37+
38+
```python
39+
from fastapi import FastAPI
40+
41+
app = FastAPI()
42+
43+
@app.get("/")
44+
def read_root():
45+
return {"message": "Hello World"}
46+
47+
@app.get("/users/{user_id}")
48+
def get_user(user_id: int):
49+
return {"user_id": user_id}
50+
51+
@app.get("/health")
52+
def health_check():
53+
return {"status": "ok"}
54+
```
55+
56+
And this test file:
57+
58+
```python
59+
def test_root_endpoint(client):
60+
response = client.get("/")
61+
assert response.status_code == 200
62+
63+
def test_get_user(client):
64+
response = client.get("/users/123")
65+
assert response.status_code == 200
66+
```
67+
68+
Running `pytest --api-cov-report` produces:
69+
70+
```
71+
API Coverage Report
72+
Uncovered Endpoints:
73+
[X] /health
74+
75+
Total API Coverage: 66.67%
76+
```
77+
78+
## Advanced Configuration
79+
80+
### Setup Wizard
81+
82+
If auto-discovery doesn't work for your project, use the interactive setup wizard:
83+
84+
```bash
85+
pytest-api-cov init
86+
```
87+
88+
This will:
89+
- Detect your framework and app location
90+
- Create a `conftest.py` fixture if needed
91+
- Generate suggested `pyproject.toml` configuration
92+
93+
### Manual Configuration
94+
95+
Create a `conftest.py` file:
96+
97+
```python
98+
import pytest
99+
from your_app import app
100+
101+
@pytest.fixture
102+
def app():
103+
return app
104+
```
105+
106+
### Configuration Options
107+
108+
Add configuration to your `pyproject.toml`:
109+
110+
```toml
111+
[tool.pytest_api_cov]
112+
# Fail if coverage is below this percentage
113+
fail_under = 80.0
114+
115+
# Control what's shown in reports
116+
show_uncovered_endpoints = true
117+
show_covered_endpoints = false
118+
show_excluded_endpoints = false
119+
120+
# Exclude endpoints from coverage using simple wildcard patterns
121+
# Use * for wildcard matching, all other characters are matched literally
122+
exclusion_patterns = [
123+
"/health", # Exact match
124+
"/metrics", # Exact match
125+
"/docs/*", # Wildcard: matches /docs/swagger, /docs/openapi, etc.
126+
"/admin/*", # Wildcard: matches all admin endpoints
127+
"/api/v1.0/*" # Exact version match (won't match /api/v1x0/*)
128+
]
129+
130+
# Save detailed JSON report
131+
report_path = "api_coverage.json"
132+
133+
# Force Unicode symbols in output
134+
force_sugar = true
135+
136+
# Force no Unicode symbols in output
137+
force_sugar_disabled = true
138+
```
139+
140+
### Command Line Options
141+
142+
```bash
143+
# Basic coverage report
144+
pytest --api-cov-report
145+
146+
# Set coverage threshold to fail test session
147+
pytest --api-cov-report --api-cov-fail-under=80
148+
149+
# Show covered endpoints
150+
pytest --api-cov-report --api-cov-show-covered-endpoints
151+
152+
# Show excluded endpoints
153+
pytest --api-cov-report --api-cov-show-excluded-endpoints
154+
155+
# Hide uncovered endpoints
156+
pytest --api-cov-report --api-cov-show-uncovered-endpoints=false
157+
158+
# Save JSON report
159+
pytest --api-cov-report --api-cov-report-path=api_coverage.json
160+
161+
# Exclude specific endpoints (supports wildcards)
162+
pytest --api-cov-report --api-cov-exclusion-patterns="/health" --api-cov-exclusion-patterns="/docs/*"
163+
164+
# Verbose logging (shows discovery process)
165+
pytest --api-cov-report -v
166+
167+
# Debug logging (very detailed)
168+
pytest --api-cov-report -vv
169+
```
170+
171+
## Framework Support
172+
173+
Works automatically with FastAPI and Flask applications.
174+
175+
### FastAPI
176+
177+
```python
178+
from fastapi import FastAPI
179+
from fastapi.testclient import TestClient
180+
181+
app = FastAPI()
182+
183+
@app.get("/items/{item_id}")
184+
def read_item(item_id: int):
185+
return {"item_id": item_id}
186+
187+
# Tests automatically get a 'client' fixture
188+
def test_read_item(client):
189+
response = client.get("/items/42")
190+
assert response.status_code == 200
191+
```
192+
193+
### Flask
194+
195+
```python
196+
from flask import Flask
197+
198+
app = Flask(__name__)
199+
200+
@app.route("/users/<int:user_id>")
201+
def get_user(user_id):
202+
return {"user_id": user_id}
203+
204+
# Tests automatically get a 'client' fixture
205+
def test_get_user(client):
206+
response = client.get("/users/123")
207+
assert response.status_code == 200
208+
```
209+
210+
## Parallel Testing
211+
212+
pytest-api-cov fully supports pytest-xdist for parallel test execution:
213+
214+
```bash
215+
# Run tests in parallel with coverage
216+
pytest --api-cov-report -n auto
217+
```
218+
219+
Coverage data is automatically collected from all worker processes and merged in the final report.
220+
221+
## JSON Report Format
222+
223+
When using `--api-cov-report-path`, the plugin generates a detailed JSON report:
224+
225+
```json
226+
{
227+
"status": 0,
228+
"coverage": 66.67,
229+
"required_coverage": 80.0,
230+
"total_endpoints": 3,
231+
"covered_count": 2,
232+
"uncovered_count": 1,
233+
"excluded_count": 0,
234+
"detail": [
235+
{
236+
"endpoint": "/",
237+
"callers": ["test_root_endpoint"]
238+
},
239+
{
240+
"endpoint": "/users/{user_id}",
241+
"callers": ["test_get_user"]
242+
},
243+
{
244+
"endpoint": "/health",
245+
"callers": []
246+
}
247+
]
248+
}
249+
```
250+
251+
## CI/CD Integration
252+
253+
### Fail on Low Coverage
254+
255+
```bash
256+
# Fail the build if coverage is below 80%
257+
pytest --api-cov-report --api-cov-fail-under=80
258+
```
259+
260+
### GitHub Actions Example
261+
262+
```yaml
263+
name: API Coverage
264+
on: [push, pull_request]
265+
266+
jobs:
267+
test:
268+
runs-on: ubuntu-latest
269+
steps:
270+
- uses: actions/checkout@v4
271+
- uses: actions/setup-python@v4
272+
with:
273+
python-version: '3.12'
274+
- run: pip install pytest pytest-api-cov
275+
- run: pytest --api-cov-report --api-cov-fail-under=80 --api-cov-report-path=coverage.json
276+
- uses: actions/upload-artifact@v4
277+
with:
278+
name: api-coverage-report
279+
path: coverage.json
280+
```
281+
282+
## Troubleshooting
283+
284+
### No App Found
285+
286+
If you see "No API app found", ensure:
287+
288+
1. Your app is in a standard location (`app.py`, `main.py`, etc.)
289+
2. Your app variable has a standard name (`app`, `application`, `main`)
290+
3. Your app imports are correct (`from fastapi import FastAPI` or `from flask import Flask`)
291+
292+
Or use the setup wizard: `pytest-api-cov init`
293+
294+
### No Endpoints Discovered
295+
296+
If you see "No endpoints discovered":
297+
298+
1. Check that your app is properly instantiated
299+
2. Verify your routes/endpoints are defined
300+
3. Ensure the `client` fixture is working in your tests
301+
4. Use `-v` or `-vv` for debug information
302+
303+
### Framework Not Detected
304+
305+
The plugin supports:
306+
- **FastAPI**: Detected by `from fastapi import` or `import fastapi`
307+
- **Flask**: Detected by `from flask import` or `import flask`
308+
309+
Other frameworks are not currently supported.
310+
311+
## License
312+
313+
This project is licensed under the Apache License 2.0.

0 commit comments

Comments
 (0)