From e9814ca7e4ca9786d5838467d22a80faa3d38729 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 20 May 2026 20:44:58 -0400 Subject: [PATCH] feat(fc3c): expose download_rate_limit_seconds + download_validate_files in /api/settings/import Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/app/api/settings.py | 17 ++++++++ tests/test_api_settings_downloader.py | 59 +++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 tests/test_api_settings_downloader.py diff --git a/backend/app/api/settings.py b/backend/app/api/settings.py index cdf0a3b..33e3878 100644 --- a/backend/app/api/settings.py +++ b/backend/app/api/settings.py @@ -20,6 +20,8 @@ _EDITABLE_FIELDS = ( "single_color_threshold", "single_color_tolerance", "phash_threshold", + "download_rate_limit_seconds", + "download_validate_files", ) @@ -38,6 +40,8 @@ async def get_import_settings(): "single_color_threshold": row.single_color_threshold, "single_color_tolerance": row.single_color_tolerance, "phash_threshold": row.phash_threshold, + "download_rate_limit_seconds": row.download_rate_limit_seconds, + "download_validate_files": row.download_validate_files, }) @@ -56,6 +60,19 @@ async def update_import_settings(): {"error": "phash_threshold must be a non-negative integer"} ), 400 + if "download_rate_limit_seconds" in body: + val = body["download_rate_limit_seconds"] + if not isinstance(val, (int, float)) or isinstance(val, bool) or val < 0: + return jsonify( + {"error": "download_rate_limit_seconds must be a non-negative number"} + ), 400 + if "download_validate_files" in body and not isinstance( + body["download_validate_files"], bool + ): + return jsonify( + {"error": "download_validate_files must be a boolean"} + ), 400 + async with get_session() as session: row = ( await session.execute(select(ImportSettings).where(ImportSettings.id == 1)) diff --git a/tests/test_api_settings_downloader.py b/tests/test_api_settings_downloader.py new file mode 100644 index 0000000..814dec8 --- /dev/null +++ b/tests/test_api_settings_downloader.py @@ -0,0 +1,59 @@ +import pytest + +from backend.app import create_app + +pytestmark = pytest.mark.integration + + +@pytest.fixture +async def app(): + return create_app() + + +@pytest.fixture +async def client(app): + async with app.test_client() as c: + yield c + + +@pytest.mark.asyncio +async def test_get_includes_downloader_fields(client): + resp = await client.get("/api/settings/import") + assert resp.status_code == 200 + body = await resp.get_json() + assert body["download_rate_limit_seconds"] == 3.0 + assert body["download_validate_files"] is True + + +@pytest.mark.asyncio +async def test_patch_download_rate_limit(client): + resp = await client.patch( + "/api/settings/import", json={"download_rate_limit_seconds": 1.5} + ) + assert resp.status_code == 200 + assert (await resp.get_json())["download_rate_limit_seconds"] == 1.5 + + +@pytest.mark.asyncio +async def test_patch_download_validate_files_false(client): + resp = await client.patch( + "/api/settings/import", json={"download_validate_files": False} + ) + assert resp.status_code == 200 + assert (await resp.get_json())["download_validate_files"] is False + + +@pytest.mark.asyncio +async def test_patch_rejects_negative_rate_limit(client): + resp = await client.patch( + "/api/settings/import", json={"download_rate_limit_seconds": -1.0} + ) + assert resp.status_code == 400 + + +@pytest.mark.asyncio +async def test_patch_rejects_non_bool_validate(client): + resp = await client.patch( + "/api/settings/import", json={"download_validate_files": "yes"} + ) + assert resp.status_code == 400