"""Centralized upload byte-limits (issue #3364). Every per-route upload limit lives in ``src.upload_limits`` as a module-level constant read through the validated ``read_byte_limit_env``. These tests pin: - the default values (unchanged from the prior per-route literals), - env-overridability for each one, - that an invalid env value fails fast (validation), and - that the routes import the constant from upload_limits rather than redefining it locally (no scattered raw getenv / hardcoded literal). """ import importlib from pathlib import Path import pytest import src.upload_limits as upload_limits REPO = Path(__file__).resolve().parent.parent # const name -> (env var, default bytes) _LIMITS = { "GALLERY_UPLOAD_MAX_BYTES": ("ODYSSEUS_GALLERY_UPLOAD_MAX_BYTES", 100 * 1024 * 1024), "GALLERY_TRANSFORM_UPLOAD_MAX_BYTES": ("ODYSSEUS_GALLERY_TRANSFORM_UPLOAD_MAX_BYTES", 25 * 1024 * 1024), "MEMORY_IMPORT_MAX_BYTES": ("ODYSSEUS_MEMORY_IMPORT_MAX_BYTES", 10 * 1024 * 1024), "PERSONAL_UPLOAD_MAX_BYTES": ("ODYSSEUS_PERSONAL_UPLOAD_MAX_BYTES", 25 * 1024 * 1024), "EMAIL_COMPOSE_UPLOAD_MAX_BYTES": ("ODYSSEUS_EMAIL_COMPOSE_UPLOAD_MAX_BYTES", 25 * 1024 * 1024), "STT_MAX_AUDIO_BYTES": ("ODYSSEUS_STT_MAX_AUDIO_BYTES", 25 * 1024 * 1024), "ICS_MAX_BYTES": ("ODYSSEUS_ICS_MAX_BYTES", 10 * 1024 * 1024), } def _reload_clean(monkeypatch): """Reload upload_limits with all the limit env vars unset.""" for env, _ in _LIMITS.values(): monkeypatch.delenv(env, raising=False) return importlib.reload(upload_limits) @pytest.fixture(autouse=True) def _restore_module(): # Ensure later tests see the env-default module, not a test-mutated reload. yield importlib.reload(upload_limits) @pytest.mark.parametrize("name,env,default", [(n, e, d) for n, (e, d) in _LIMITS.items()]) def test_default_value(monkeypatch, name, env, default): mod = _reload_clean(monkeypatch) assert getattr(mod, name) == default @pytest.mark.parametrize("name,env,default", [(n, e, d) for n, (e, d) in _LIMITS.items()]) def test_env_override(monkeypatch, name, env, default): for e, _ in _LIMITS.values(): monkeypatch.delenv(e, raising=False) monkeypatch.setenv(env, "4242") mod = importlib.reload(upload_limits) assert getattr(mod, name) == 4242 @pytest.mark.parametrize("env", [e for e, _ in _LIMITS.values()]) def test_invalid_env_fails_fast(monkeypatch, env): for e, _ in _LIMITS.values(): monkeypatch.delenv(e, raising=False) monkeypatch.setenv(env, "not-an-int") with pytest.raises(ValueError, match=env): importlib.reload(upload_limits) @pytest.mark.parametrize("env", [e for e, _ in _LIMITS.values()]) def test_non_positive_env_rejected(monkeypatch, env): for e, _ in _LIMITS.values(): monkeypatch.delenv(e, raising=False) monkeypatch.setenv(env, "0") with pytest.raises(ValueError, match="greater than 0"): importlib.reload(upload_limits) def test_routes_import_from_upload_limits_not_local_defs(): """Routes must import the constant, not redefine it via raw getenv / literal.""" forbidden = { "routes/gallery_routes.py": [ 'int(os.getenv("ODYSSEUS_GALLERY_UPLOAD_MAX_BYTES"', 'int(os.getenv("ODYSSEUS_GALLERY_TRANSFORM_UPLOAD_MAX_BYTES"', ], "routes/memory_routes.py": ['int(os.getenv("ODYSSEUS_MEMORY_IMPORT_MAX_BYTES"'], "routes/personal_routes.py": ['os.getenv("ODYSSEUS_PERSONAL_UPLOAD_MAX_BYTES"'], "routes/email_routes.py": ["EMAIL_COMPOSE_UPLOAD_MAX_BYTES = 25 * 1024 * 1024"], "routes/stt_routes.py": ["STT_MAX_AUDIO_BYTES = 25 * 1024 * 1024"], "routes/calendar_routes.py": ["_ICS_MAX_BYTES = 10 * 1024 * 1024"], } for path, needles in forbidden.items(): text = (REPO / path).read_text(encoding="utf-8") for needle in needles: assert needle not in text, f"{path} still defines limit locally: {needle}" # And each imports from upload_limits. imports = { "routes/gallery_routes.py": "GALLERY_UPLOAD_MAX_BYTES", "routes/memory_routes.py": "MEMORY_IMPORT_MAX_BYTES", "routes/personal_routes.py": "PERSONAL_UPLOAD_MAX_BYTES", "routes/email_routes.py": "EMAIL_COMPOSE_UPLOAD_MAX_BYTES", "routes/stt_routes.py": "STT_MAX_AUDIO_BYTES", "routes/calendar_routes.py": "ICS_MAX_BYTES", } for path, const in imports.items(): text = (REPO / path).read_text(encoding="utf-8") assert "from src.upload_limits import" in text assert const in text