diff --git a/abogen/webui/app.py b/abogen/webui/app.py index 2a336b9..c79aac1 100644 --- a/abogen/webui/app.py +++ b/abogen/webui/app.py @@ -84,6 +84,12 @@ def create_app(config: Optional[dict[str, Any]] = None) -> Flask: "UPLOAD_FOLDER": str(uploads_dir), "OUTPUT_FOLDER": str(outputs_dir), "MAX_CONTENT_LENGTH": 1024 * 1024 * 400, # 400 MB uploads + # Large books can submit four form fields per chapter. Werkzeug's + # defaults reject those requests before the wizard route can process + # them, even though the encoded payload is much smaller than the upload + # limit above. + "MAX_FORM_MEMORY_SIZE": 10 * 1024 * 1024, + "MAX_FORM_PARTS": 10_000, } if config: base_config.update(config) diff --git a/pyproject.toml b/pyproject.toml index 3722fb5..884cd98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ dependencies = [ "python-dotenv>=1.0.1", "static_ffmpeg>=2.13", "Markdown>=3.9", - "Flask>=3.0.3", + "Flask>=3.1.0", "numpy>=1.24.0", "gpustat>=1.1.1", "num2words>=0.5.13", diff --git a/tests/test_webui_form_limits.py b/tests/test_webui_form_limits.py new file mode 100644 index 0000000..d917455 --- /dev/null +++ b/tests/test_webui_form_limits.py @@ -0,0 +1,53 @@ +from abogen.webui.app import create_app + + +def _large_chapter_form() -> dict[str, str]: + data = {"step": "chapters"} + for index in range(370): + prefix = f"chapter-{index}" + data[f"{prefix}-enabled"] = "on" + data[f"{prefix}-title"] = f"Chapter {index} " + ("x" * 1400) + data[f"{prefix}-voice"] = "af_heart" + data[f"{prefix}-formula"] = "default" + return data + + +def test_large_chapter_form_reaches_wizard_route(tmp_path): + app = create_app( + { + "TESTING": True, + "SECRET_KEY": "test", + "OUTPUT_FOLDER": str(tmp_path / "output"), + "UPLOAD_FOLDER": str(tmp_path / "uploads"), + } + ) + + with app.test_client() as client: + response = client.post( + "/wizard/update?format=json", + data=_large_chapter_form(), + ) + + assert response.status_code == 400 + assert response.get_json()["error"] == "Missing job ID" + + +def test_large_multipart_chapter_form_reaches_wizard_route(tmp_path): + app = create_app( + { + "TESTING": True, + "SECRET_KEY": "test", + "OUTPUT_FOLDER": str(tmp_path / "output"), + "UPLOAD_FOLDER": str(tmp_path / "uploads"), + } + ) + + with app.test_client() as client: + response = client.post( + "/wizard/update?format=json", + data=_large_chapter_form(), + content_type="multipart/form-data", + ) + + assert response.status_code == 400 + assert response.get_json()["error"] == "Missing job ID"