2026-06-04 01:32:50 +02:00
|
|
|
import io
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from fastapi import HTTPException, UploadFile
|
|
|
|
|
|
|
|
|
|
from src.upload_limits import format_byte_limit, read_upload_limited
|
|
|
|
|
|
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _upload(name: str, data: bytes) -> UploadFile:
|
|
|
|
|
return UploadFile(filename=name, file=io.BytesIO(data))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _source(path: str) -> str:
|
|
|
|
|
return (REPO / path).read_text(encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_read_upload_limited_accepts_exact_limit():
|
|
|
|
|
assert await read_upload_limited(_upload("ok.bin", b"abcd"), 4, "Test upload") == b"abcd"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_read_upload_limited_rejects_oversized_upload():
|
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
|
|
|
await read_upload_limited(_upload("too-big.bin", b"abcde"), 4, "Test upload")
|
|
|
|
|
|
|
|
|
|
assert exc.value.status_code == 413
|
|
|
|
|
assert exc.value.detail == "Test upload exceeds 4 bytes limit"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_upload_limit_formatting_is_human_readable():
|
|
|
|
|
assert format_byte_limit(25 * 1024 * 1024) == "25 MB"
|
|
|
|
|
assert format_byte_limit(512 * 1024) == "512 KB"
|
|
|
|
|
assert format_byte_limit(7) == "7 bytes"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_direct_upload_routes_use_bounded_reads():
|
|
|
|
|
expectations = {
|
|
|
|
|
"routes/stt_routes.py": [
|
|
|
|
|
"read_upload_limited(file, STT_MAX_AUDIO_BYTES",
|
|
|
|
|
],
|
2026-06-28 17:40:34 +08:00
|
|
|
"routes/gallery/gallery_routes.py": [
|
2026-06-04 01:32:50 +02:00
|
|
|
"read_upload_limited(file, GALLERY_UPLOAD_MAX_BYTES",
|
|
|
|
|
"read_upload_limited(file, GALLERY_TRANSFORM_UPLOAD_MAX_BYTES",
|
|
|
|
|
],
|
refactor(routes): move memory domain into routes/memory/ subpackage (#5007)
Slice 2c of the route-domain reorganization (#4082/#4071, per
specs/architecture-runtime-inventory.md §6.3). Moves memory_routes.py into
routes/memory/, leaving a backward-compat sys.modules shim at the old path.
Pure file reorganization, no behavior change.
The shim uses sys.modules replacement (same pattern as the merged gallery
#4903 and research #4975 slices) so that `import routes.memory_routes`,
`from routes.memory_routes import X`, `importlib.import_module(...)`, and
the `import ... as mr` + `monkeypatch.setattr(mr, ...)` pattern used by
test_memory_routes_session_owner.py / test_memory_owner_isolation.py all
operate on the same module object the application uses.
The canonical module does NOT depend on the shim — routes/memory/
memory_routes.py imports only from services/, core/, src/, and stdlib (zero
internal routes/ coupling).
Four source-introspection test sites repointed to the new canonical path:
- test_direct_upload_limits.py
- test_upload_limits_centralized.py (two dict keys)
- test_vision_owner_scope.py
Adds tests/test_memory_routes_shim.py to pin the sys.modules shim contract
(legacy and canonical paths resolve to the same module object; monkeypatch
via legacy alias reaches the canonical module).
Verified: compileall clean; full suite 4219 passed, 3 skipped.
2026-06-30 23:52:14 +08:00
|
|
|
"routes/memory/memory_routes.py": [
|
2026-06-04 01:32:50 +02:00
|
|
|
"read_upload_limited(file, MEMORY_IMPORT_MAX_BYTES",
|
|
|
|
|
],
|
|
|
|
|
"routes/calendar_routes.py": [
|
refactor(uploads): centralize upload byte-limits in upload_limits.py (#3364) (#3518)
Move every per-route upload byte-limit into src/upload_limits.py as a
validated, env-overridable constant via read_byte_limit_env:
- Add GALLERY_UPLOAD_MAX_BYTES, GALLERY_TRANSFORM_UPLOAD_MAX_BYTES,
MEMORY_IMPORT_MAX_BYTES, PERSONAL_UPLOAD_MAX_BYTES,
EMAIL_COMPOSE_UPLOAD_MAX_BYTES, STT_MAX_AUDIO_BYTES, ICS_MAX_BYTES.
- Routes import their constant instead of defining it locally: replaces 4
raw int(os.getenv(...)) and removes 3 hardcoded literals.
- The 3 previously-hardcoded limits (email compose, STT audio, calendar
ICS) are now env-overridable with the same ODYSSEUS_*_MAX_BYTES naming.
- Defaults unchanged, so behavior is unchanged unless an env var is set;
an invalid value now fails fast with a clear message instead of a bare
int() ValueError.
- Document all env vars in .env.example and the README.
Fixes #3364
2026-06-09 01:24:30 +02:00
|
|
|
"read_upload_limited(file, ICS_MAX_BYTES",
|
2026-06-04 01:32:50 +02:00
|
|
|
],
|
|
|
|
|
"routes/email_routes.py": [
|
|
|
|
|
"read_upload_limited(file, EMAIL_COMPOSE_UPLOAD_MAX_BYTES",
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for path, needles in expectations.items():
|
|
|
|
|
text = _source(path)
|
|
|
|
|
for needle in needles:
|
|
|
|
|
assert needle in text
|