"""Tests for the request body-size measurement hook and the leak-free 413 handler. The body limit is enforced by Werkzeug from ``app.config['MAX_CONTENT_LENGTH']``; this module adds two things on top: a passive measurement that records the observed ``Content-Length`` of every request carrying a body (so the limit can be right-sized from real traffic) and a generic 413 handler that never leaks the configured limit to the client. """ import pytest from flask import Flask, request from core.config import Config from core.hardening import observability as obs from core.hardening.body_limit import setup_body_limit def _app(view, max_bytes: int | None = None) -> Flask: app = Flask(__name__) if max_bytes is not None: app.config['MAX_CONTENT_LENGTH'] = max_bytes setup_body_limit(app, Config) app.add_url_rule('/echo', 'echo', view, methods=['GET', 'POST']) return app def test_request_body_size_is_measured(monkeypatch: pytest.MonkeyPatch) -> None: """A request carrying a body records its Content-Length, tagged by the matched endpoint.""" seen: list[tuple[int, str]] = [] monkeypatch.setattr( obs, 'request_body_bytes', lambda size, endpoint: seen.append((size, endpoint)) ) _app(lambda: '').test_client().post('/echo', data=b'hello') assert seen == [(5, 'echo')] def test_bodyless_request_is_not_measured(monkeypatch: pytest.MonkeyPatch) -> None: """A request with no body (no Content-Length) emits no body-size measurement.""" seen: list[tuple[int, str]] = [] monkeypatch.setattr( obs, 'request_body_bytes', lambda size, endpoint: seen.append((size, endpoint)) ) _app(lambda: '').test_client().get('/echo') assert seen == [] def test_oversized_body_returns_generic_413(monkeypatch: pytest.MonkeyPatch) -> None: """A body over the limit yields 413 with no configured-limit value leaked in the response.""" monkeypatch.setattr(obs, 'request_body_bytes', lambda size, endpoint: None) def view(): request.get_data() # touching the body triggers Werkzeug's length check return '' res = _app(view, max_bytes=10).test_client().post('/echo', data=b'x' * 50) assert res.status_code == 413 assert b'10' not in res.data # the configured limit is never disclosed assert b'50' not in res.data def test_oversized_body_counts_the_rejection(monkeypatch: pytest.MonkeyPatch) -> None: """The 413 handler increments the body-too-large counter exactly once.""" monkeypatch.setattr(obs, 'request_body_bytes', lambda size, endpoint: None) rejections: list[int] = [] monkeypatch.setattr(obs, 'body_too_large', lambda: rejections.append(1)) def view(): request.get_data() return '' _app(view, max_bytes=10).test_client().post('/echo', data=b'x' * 50) assert rejections == [1]