* fix(longform): correctness + robustness fixes from adversarial review Fixes the confirmed findings from a multi-agent review of the convergence: HIGH (correctness/output): - MP3 + cover produced a corrupt file (-map 2:v -c:v copy is invalid for mp3). Cover art is now embedded for M4B only; mp3 skips it (m4b is the cover format). - Chapter cache key omitted ref_text — editing only a profile's ref_text served stale audio. ref_text is now part of the voice signature. - Preview wrote audiobook_cache/ but the render reads longform_cache/ (rename missed in PR 5) → cache-warming silently broke. Unified to longform_cache/. Robustness (DoS/OOM guards): - /audiobook/import caps upload at 64 MB; epub_to_chapter_script bounds per-entry (25 MB) and cumulative (300 MB) uncompressed reads (zip-bomb guard). - /longform/render rejects > 10,000 chapters (422). Frontend leaks: - StoriesEditor.removeTrack revokes the line's preview blob URL. - AudiobookTab revokes the cover blob URL on replace/unmount. Deferred fast-follows (also from review): render-cache disk eviction; restoring the standalone chapter cue-sheet export (needs chapter times in the done event). Tests: mp3-drops-cover, epub entry/total caps, import + chapter-count limits; updated the cache-hit test for the 4-field voice sig. 70 backend + 334 frontend green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(longform): pass EPUB caps as params, not monkeypatch (CI import-path fix) The cap tests monkeypatched module constants, but in the full-suite CI context the module loads under a different import path so the patch missed the function (it used the real 300 MB cap → tests failed). epub_to_chapter_script now takes max_entry_bytes/max_total_bytes kwargs (default to the constants); tests pass small values directly — deterministic regardless of import path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
41 lines
1.2 KiB
Python
41 lines
1.2 KiB
Python
"""Input bounds on the longform endpoints (review findings #4/#5).
|
|
|
|
Direct handler calls (no main/torch import). Caps are monkeypatched small so the
|
|
tests stay cheap.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import io
|
|
|
|
import pytest
|
|
from fastapi import HTTPException, UploadFile
|
|
|
|
import api.routers.audiobook as ab
|
|
from api.routers.audiobook import (
|
|
LongformChapter,
|
|
LongformRenderRequest,
|
|
LongformSpan,
|
|
audiobook_import,
|
|
longform_render,
|
|
)
|
|
|
|
|
|
def test_import_rejects_oversize(monkeypatch):
|
|
monkeypatch.setattr(ab, "_IMPORT_MAX_BYTES", 10)
|
|
up = UploadFile(io.BytesIO(b"x" * 50), filename="big.txt")
|
|
with pytest.raises(HTTPException) as ei:
|
|
asyncio.run(audiobook_import(up))
|
|
assert ei.value.status_code == 400
|
|
|
|
|
|
def test_longform_render_rejects_too_many_chapters(monkeypatch):
|
|
monkeypatch.setattr(ab, "_MAX_CHAPTERS", 1)
|
|
req = LongformRenderRequest(chapters=[
|
|
LongformChapter(title="A", spans=[LongformSpan(text="hi")]),
|
|
LongformChapter(title="B", spans=[LongformSpan(text="yo")]),
|
|
])
|
|
with pytest.raises(HTTPException) as ei:
|
|
asyncio.run(longform_render(req))
|
|
assert ei.value.status_code == 422
|