agrovision / tests /integration /test_security.py
alexp97's picture
feat(seguridad): rate limiting en /api + cabeceras de hardening (Fase 10)
7afd4d9
Raw
History Blame Contribute Delete
2 kB
"""
Archivo: test_security.py
Fecha de modificación: 04/06/2026
Autor: Equipo AgroVisión
Descripción:
Pruebas del *hardening* del gateway: cabeceras de seguridad en las respuestas y el
rate limiting de `/api` (429 al exceder el cupo). Recrea la app con un límite bajo.
Ejecución:
uv run python -m pytest tests/integration/test_security.py
"""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from backend.config import get_settings
@pytest.fixture(autouse=True)
def _reset(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("COUNTING_ENABLED", raising=False)
get_settings.cache_clear()
yield
get_settings.cache_clear()
def test_cabeceras_de_seguridad_presentes() -> None:
"""Toda respuesta incluye las cabeceras de hardening."""
from backend.main import create_app
with TestClient(create_app()) as client:
r = client.get("/api/status")
assert r.headers.get("X-Content-Type-Options") == "nosniff"
assert r.headers.get("X-Frame-Options") == "SAMEORIGIN"
assert "Referrer-Policy" in r.headers
def test_rate_limit_devuelve_429(monkeypatch: pytest.MonkeyPatch) -> None:
"""Al exceder el cupo de /api, el gateway responde 429 con Retry-After."""
monkeypatch.setenv("RATE_LIMIT_PER_MIN", "3")
get_settings.cache_clear()
from backend.main import create_app
with TestClient(create_app()) as client:
codes = [client.get("/api/status").status_code for _ in range(4)]
assert codes[:3] == [200, 200, 200]
assert codes[3] == 429
def test_rate_limit_desactivado_no_bloquea(monkeypatch: pytest.MonkeyPatch) -> None:
"""Con RATE_LIMIT_PER_MIN=0 no se aplica límite."""
monkeypatch.setenv("RATE_LIMIT_PER_MIN", "0")
get_settings.cache_clear()
from backend.main import create_app
with TestClient(create_app()) as client:
codes = [client.get("/api/status").status_code for _ in range(10)]
assert all(c == 200 for c in codes)