import pytest pytestmark = pytest.mark.integration @pytest.mark.asyncio async def test_get_import_settings_defaults(client): resp = await client.get("/api/settings/import") assert resp.status_code == 200 body = await resp.get_json() # Defaults from the migration: assert body["min_width"] == 0 assert body["min_height"] == 0 assert body["skip_transparent"] is False assert body["transparency_threshold"] == 0.9 @pytest.mark.asyncio async def test_patch_import_settings(client): resp = await client.patch( "/api/settings/import", json={"min_width": 500, "skip_transparent": True, "transparency_threshold": 0.7}, ) assert resp.status_code == 200 body = await resp.get_json() assert body["min_width"] == 500 assert body["skip_transparent"] is True assert body["transparency_threshold"] == 0.7 @pytest.mark.asyncio async def test_translation_settings_defaults_and_patch(client): # #143: translation is OFF by default with NO default host — the operator # points it at their own Interpreter proxy and enables it. body = await (await client.get("/api/settings/import")).get_json() assert body["translation_enabled"] is False assert body["interpreter_base_url"] == "" assert body["translation_target_lang"] == "en" ok = await client.patch("/api/settings/import", json={ "translation_enabled": True, "interpreter_base_url": "http://interpreter.lan", }) assert ok.status_code == 200 out = await ok.get_json() assert out["translation_enabled"] is True assert out["interpreter_base_url"] == "http://interpreter.lan" # Type validation. assert (await client.patch( "/api/settings/import", json={"translation_enabled": "yes"})).status_code == 400 assert (await client.patch( "/api/settings/import", json={"interpreter_base_url": 123})).status_code == 400 @pytest.mark.asyncio async def test_translation_status_defaults(client): # Off + no URL → no network health call, healthy False (#143). body = await (await client.get("/api/settings/translation/status")).get_json() assert body["enabled"] is False assert body["base_url_set"] is False assert body["healthy"] is False assert "untranslated_count" in body @pytest.mark.asyncio async def test_translation_run_requires_config(client): resp = await client.post("/api/settings/translation/run") assert resp.status_code == 400 # disabled / no base URL @pytest.mark.asyncio async def test_translation_test_connection(client, monkeypatch): # Tests a GIVEN url (not the saved one) without persisting anything. monkeypatch.setattr("backend.app.api.settings.ic.health", lambda *a, **k: True) resp = await client.post( "/api/settings/translation/test", json={"base_url": "http://i.lan"}) assert resp.status_code == 200 assert (await resp.get_json())["healthy"] is True # Empty URL → False, no health call. empty = await client.post( "/api/settings/translation/test", json={"base_url": ""}) assert (await empty.get_json())["healthy"] is False @pytest.mark.asyncio async def test_translation_run_enqueues_when_configured(client, monkeypatch): monkeypatch.setattr( "backend.app.tasks.translation.translate_posts.delay", lambda *a, **k: type("R", (), {"id": "t1"})(), ) await client.patch("/api/settings/import", json={ "translation_enabled": True, "interpreter_base_url": "http://i.lan", }) resp = await client.post("/api/settings/translation/run") assert resp.status_code == 202 assert (await resp.get_json())["celery_task_id"] == "t1" @pytest.mark.asyncio async def test_patch_rejects_non_object(client): resp = await client.patch("/api/settings/import", json=[1, 2, 3]) assert resp.status_code == 400 @pytest.mark.asyncio async def test_import_settings_phash_threshold_default(client): resp = await client.get("/api/settings/import") assert resp.status_code == 200 assert (await resp.get_json())["phash_threshold"] == 10 @pytest.mark.asyncio async def test_patch_phash_threshold(client): resp = await client.patch( "/api/settings/import", json={"phash_threshold": 4} ) assert resp.status_code == 200 assert (await resp.get_json())["phash_threshold"] == 4 @pytest.mark.asyncio async def test_patch_phash_threshold_rejects_negative(client): resp = await client.patch( "/api/settings/import", json={"phash_threshold": -1} ) assert resp.status_code == 400 @pytest.mark.asyncio async def test_system_stats_shape(client): resp = await client.get("/api/system/stats") assert resp.status_code == 200 body = await resp.get_json() for key in ("total_images", "total_tags", "storage_bytes", "subscription_count", "tasks", "active_batch"): assert key in body for status in ("pending", "queued", "processing", "complete", "skipped", "failed"): assert status in body["tasks"] # --- FC-3d: scheduling knobs ---------------------------------------------- @pytest.mark.asyncio async def test_get_includes_scheduling_defaults(client): resp = await client.get("/api/settings/import") body = await resp.get_json() assert body["download_schedule_default_seconds"] == 28800 assert body["download_event_retention_days"] == 90 assert body["download_failure_warning_threshold"] == 5 @pytest.mark.asyncio async def test_patch_scheduling_defaults(client): resp = await client.patch("/api/settings/import", json={ "download_schedule_default_seconds": 7200, "download_event_retention_days": 30, "download_failure_warning_threshold": 3, }) assert resp.status_code == 200 body = await resp.get_json() assert body["download_schedule_default_seconds"] == 7200 assert body["download_event_retention_days"] == 30 assert body["download_failure_warning_threshold"] == 3 @pytest.mark.asyncio async def test_patch_rejects_below_min_interval(client): resp = await client.patch( "/api/settings/import", json={"download_schedule_default_seconds": 30}, ) assert resp.status_code == 400 @pytest.mark.asyncio async def test_patch_rejects_above_max_interval(client): resp = await client.patch( "/api/settings/import", json={"download_schedule_default_seconds": 90000}, ) assert resp.status_code == 400 @pytest.mark.asyncio async def test_patch_rejects_zero_retention_days(client): resp = await client.patch( "/api/settings/import", json={"download_event_retention_days": 0}, ) assert resp.status_code == 400 @pytest.mark.asyncio async def test_patch_rejects_zero_failure_threshold(client): resp = await client.patch( "/api/settings/import", json={"download_failure_warning_threshold": 0}, ) assert resp.status_code == 400 @pytest.mark.asyncio async def test_system_stats_integrity_counts(client, db): from backend.app.models import ImageRecord base = "stats_" + "0" * 56 for i, status in enumerate( ["ok", "ok", "corrupt", "failed_verification"] ): db.add(ImageRecord( path=f"/images/i/{i}.jpg", sha256=base + f"{i:02d}", size_bytes=1, mime="image/jpeg", width=1, height=1, origin="imported_filesystem", integrity_status=status, )) await db.commit() resp = await client.get("/api/system/stats") assert resp.status_code == 200 body = await resp.get_json() integ = body["integrity"] assert integ["ok"] >= 2 assert integ["corrupt"] >= 1 assert integ["failed_verification"] >= 1 assert "unknown" in integ