From 7fadbfa9920617f710a16322a44ab8f1a714d5d5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 09:19:19 +0000 Subject: [PATCH 1/5] Add comprehensive unit tests for app/utils/settings_sync.py Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- run_fast_tests.sh | 2 + tests/test_settings_sync.py | 207 ++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100755 run_fast_tests.sh create mode 100644 tests/test_settings_sync.py diff --git a/run_fast_tests.sh b/run_fast_tests.sh new file mode 100755 index 00000000..ae146652 --- /dev/null +++ b/run_fast_tests.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pytest tests/ -k "not test_e2e_full_stack and not test_upload_tasks and not test_slow" -m "not slow" -n 4 diff --git a/tests/test_settings_sync.py b/tests/test_settings_sync.py new file mode 100644 index 00000000..19f4c94b --- /dev/null +++ b/tests/test_settings_sync.py @@ -0,0 +1,207 @@ +import pytest +from unittest.mock import patch, MagicMock + +import app.utils.settings_sync +from app.utils.settings_sync import ( + notify_settings_updated, + register_settings_reload_signal, + SETTINGS_VERSION_KEY, +) + + +@pytest.fixture +def reset_last_seen_version(): + """Reset the global variable before and after tests.""" + app.utils.settings_sync._last_seen_version = "" + yield + app.utils.settings_sync._last_seen_version = "" + + +@patch("app.utils.settings_sync.redis.from_url") +@patch("app.utils.config_loader.reload_settings_from_db") +@patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") +@patch("app.utils.settings_sync.time.time", return_value=12345.0) +def test_notify_settings_updated_success(mock_time, mock_ensure_ocr, mock_reload, mock_redis): + # Setup mock redis instance + mock_redis_instance = MagicMock() + mock_redis.return_value = mock_redis_instance + + notify_settings_updated() + + # Verify redis calls + mock_redis.assert_called_once() + mock_redis_instance.set.assert_called_once_with(SETTINGS_VERSION_KEY, "12345.0") + + # Verify other calls + mock_reload.assert_called_once() + mock_ensure_ocr.assert_called_once() + + +@patch("app.utils.settings_sync.redis.from_url") +@patch("app.utils.config_loader.reload_settings_from_db") +@patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") +def test_notify_settings_updated_redis_failure(mock_ensure_ocr, mock_reload, mock_redis, caplog): + # Setup mock redis to fail + mock_redis.side_effect = Exception("Redis connection failed") + + notify_settings_updated() + + # Verification: should continue and call reload and ocr despite redis failure + mock_reload.assert_called_once() + mock_ensure_ocr.assert_called_once() + assert "Could not publish settings update to Redis: Redis connection failed" in caplog.text + + +@patch("app.utils.settings_sync.redis.from_url") +@patch("app.utils.config_loader.reload_settings_from_db") +@patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") +def test_notify_settings_updated_reload_failure(mock_ensure_ocr, mock_reload, mock_redis, caplog): + # Setup reload to fail + mock_reload.side_effect = Exception("Reload failed") + + mock_redis_instance = MagicMock() + mock_redis.return_value = mock_redis_instance + + notify_settings_updated() + + # Verification: redis should be called, reload fails, ocr should still be called + mock_redis_instance.set.assert_called_once() + mock_ensure_ocr.assert_called_once() + assert "Could not reload in-process settings: Reload failed" in caplog.text + + +@patch("app.utils.settings_sync.redis.from_url") +@patch("app.utils.config_loader.reload_settings_from_db") +@patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") +def test_notify_settings_updated_ocr_failure(mock_ensure_ocr, mock_reload, mock_redis, caplog): + # Setup ocr check to fail + mock_ensure_ocr.side_effect = Exception("OCR check failed") + + mock_redis_instance = MagicMock() + mock_redis.return_value = mock_redis_instance + + notify_settings_updated() + + # Verification: all should be called, ocr failure logged + mock_redis_instance.set.assert_called_once() + mock_reload.assert_called_once() + assert "Could not schedule OCR language check: OCR check failed" in caplog.text + + +@patch("app.utils.settings_sync.task_prerun.connect") +def test_register_settings_reload_signal(mock_connect): + register_settings_reload_signal() + # It should register a signal with task_prerun + mock_connect.assert_called_once_with(weak=False) + + +@patch("app.utils.settings_sync.task_prerun.connect") +@patch("app.utils.settings_sync.redis.from_url") +@patch("app.utils.config_loader.reload_settings_from_db") +@patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") +def test_reload_if_stale_new_version(mock_ensure_ocr, mock_reload, mock_redis, mock_connect, reset_last_seen_version): + # Capture the registered callback + mock_decorator = MagicMock() + mock_connect.return_value = mock_decorator + + register_settings_reload_signal() + + mock_connect.assert_called_once_with(weak=False) + # Get the callback function + callback = mock_decorator.call_args[0][0] + + # Setup redis to return a new version + mock_redis_instance = MagicMock() + mock_redis_instance.get.return_value = b"new_version" + mock_redis.return_value = mock_redis_instance + + # Initial state check + assert app.utils.settings_sync._last_seen_version == "" + + # Call the callback + callback(sender="test") + + # Verification + mock_redis_instance.get.assert_called_once_with(SETTINGS_VERSION_KEY) + mock_reload.assert_called_once() + mock_ensure_ocr.assert_called_once() + assert app.utils.settings_sync._last_seen_version == "new_version" + + +@patch("app.utils.settings_sync.task_prerun.connect") +@patch("app.utils.settings_sync.redis.from_url") +@patch("app.utils.config_loader.reload_settings_from_db") +@patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") +def test_reload_if_stale_same_version(mock_ensure_ocr, mock_reload, mock_redis, mock_connect, reset_last_seen_version): + # Set initial state + app.utils.settings_sync._last_seen_version = "existing_version" + + mock_decorator = MagicMock() + mock_connect.return_value = mock_decorator + register_settings_reload_signal() + callback = mock_decorator.call_args[0][0] + + # Setup redis to return the SAME version + mock_redis_instance = MagicMock() + mock_redis_instance.get.return_value = b"existing_version" + mock_redis.return_value = mock_redis_instance + + # Call the callback + callback(sender="test") + + # Verification + mock_redis_instance.get.assert_called_once_with(SETTINGS_VERSION_KEY) + # Should NOT reload or check OCR + mock_reload.assert_not_called() + mock_ensure_ocr.assert_not_called() + assert app.utils.settings_sync._last_seen_version == "existing_version" + + +@patch("app.utils.settings_sync.task_prerun.connect") +@patch("app.utils.settings_sync.redis.from_url") +@patch("app.utils.config_loader.reload_settings_from_db") +def test_reload_if_stale_redis_error(mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog): + import logging + caplog.set_level(logging.DEBUG) + mock_decorator = MagicMock() + mock_connect.return_value = mock_decorator + register_settings_reload_signal() + callback = mock_decorator.call_args[0][0] + + # Setup redis to fail + mock_redis.side_effect = Exception("Redis error") + + # Call the callback + callback(sender="test") + + # Verification + mock_reload.assert_not_called() + assert "Settings version check skipped: Redis error" in caplog.text + + +@patch("app.utils.settings_sync.task_prerun.connect") +@patch("app.utils.settings_sync.redis.from_url") +@patch("app.utils.config_loader.reload_settings_from_db") +@patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") +def test_reload_if_stale_ocr_error(mock_ensure_ocr, mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog): + mock_decorator = MagicMock() + mock_connect.return_value = mock_decorator + register_settings_reload_signal() + callback = mock_decorator.call_args[0][0] + + # Setup redis to return a new version + mock_redis_instance = MagicMock() + mock_redis_instance.get.return_value = b"new_version" + mock_redis.return_value = mock_redis_instance + + # Setup OCR check to fail + mock_ensure_ocr.side_effect = Exception("OCR error") + + # Call the callback + callback(sender="test") + + # Verification + mock_reload.assert_called_once() + mock_ensure_ocr.assert_called_once() + assert "Could not schedule OCR language check on worker: OCR error" in caplog.text + assert app.utils.settings_sync._last_seen_version == "new_version" From 275a5ad6fa887ce3aa05d50787bd5da435735f26 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Mar 2026 09:19:51 +0000 Subject: [PATCH 2/5] style: apply ruff auto-fix - Auto-formatted code with ruff format - Applied ruff linting fixes with --fix Co-authored-by: github-actions[bot] --- tests/test_settings_sync.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_settings_sync.py b/tests/test_settings_sync.py index 19f4c94b..ea23f999 100644 --- a/tests/test_settings_sync.py +++ b/tests/test_settings_sync.py @@ -1,11 +1,12 @@ +from unittest.mock import MagicMock, patch + import pytest -from unittest.mock import patch, MagicMock import app.utils.settings_sync from app.utils.settings_sync import ( + SETTINGS_VERSION_KEY, notify_settings_updated, register_settings_reload_signal, - SETTINGS_VERSION_KEY, ) @@ -162,6 +163,7 @@ def test_reload_if_stale_same_version(mock_ensure_ocr, mock_reload, mock_redis, @patch("app.utils.config_loader.reload_settings_from_db") def test_reload_if_stale_redis_error(mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog): import logging + caplog.set_level(logging.DEBUG) mock_decorator = MagicMock() mock_connect.return_value = mock_decorator @@ -183,7 +185,9 @@ def test_reload_if_stale_redis_error(mock_reload, mock_redis, mock_connect, rese @patch("app.utils.settings_sync.redis.from_url") @patch("app.utils.config_loader.reload_settings_from_db") @patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") -def test_reload_if_stale_ocr_error(mock_ensure_ocr, mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog): +def test_reload_if_stale_ocr_error( + mock_ensure_ocr, mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog +): mock_decorator = MagicMock() mock_connect.return_value = mock_decorator register_settings_reload_signal() From dab881b9b6b18ecf8c8c6ea899cb3423e4c59c82 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 09:22:46 +0000 Subject: [PATCH 3/5] Fix ruff linting errors in test_settings_sync.py Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- tests/test_settings_sync.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_settings_sync.py b/tests/test_settings_sync.py index ea23f999..37b0754d 100644 --- a/tests/test_settings_sync.py +++ b/tests/test_settings_sync.py @@ -163,7 +163,6 @@ def test_reload_if_stale_same_version(mock_ensure_ocr, mock_reload, mock_redis, @patch("app.utils.config_loader.reload_settings_from_db") def test_reload_if_stale_redis_error(mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog): import logging - caplog.set_level(logging.DEBUG) mock_decorator = MagicMock() mock_connect.return_value = mock_decorator @@ -185,9 +184,7 @@ def test_reload_if_stale_redis_error(mock_reload, mock_redis, mock_connect, rese @patch("app.utils.settings_sync.redis.from_url") @patch("app.utils.config_loader.reload_settings_from_db") @patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") -def test_reload_if_stale_ocr_error( - mock_ensure_ocr, mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog -): +def test_reload_if_stale_ocr_error(mock_ensure_ocr, mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog): mock_decorator = MagicMock() mock_connect.return_value = mock_decorator register_settings_reload_signal() From 8bb6457c65c177a13fd15de7a7b55e24eb39477e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Mar 2026 09:23:40 +0000 Subject: [PATCH 4/5] style: apply ruff auto-fix - Auto-formatted code with ruff format - Applied ruff linting fixes with --fix Co-authored-by: github-actions[bot] --- tests/test_settings_sync.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_settings_sync.py b/tests/test_settings_sync.py index 37b0754d..ea23f999 100644 --- a/tests/test_settings_sync.py +++ b/tests/test_settings_sync.py @@ -163,6 +163,7 @@ def test_reload_if_stale_same_version(mock_ensure_ocr, mock_reload, mock_redis, @patch("app.utils.config_loader.reload_settings_from_db") def test_reload_if_stale_redis_error(mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog): import logging + caplog.set_level(logging.DEBUG) mock_decorator = MagicMock() mock_connect.return_value = mock_decorator @@ -184,7 +185,9 @@ def test_reload_if_stale_redis_error(mock_reload, mock_redis, mock_connect, rese @patch("app.utils.settings_sync.redis.from_url") @patch("app.utils.config_loader.reload_settings_from_db") @patch("app.utils.ocr_language_manager.ensure_ocr_languages_async") -def test_reload_if_stale_ocr_error(mock_ensure_ocr, mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog): +def test_reload_if_stale_ocr_error( + mock_ensure_ocr, mock_reload, mock_redis, mock_connect, reset_last_seen_version, caplog +): mock_decorator = MagicMock() mock_connect.return_value = mock_decorator register_settings_reload_signal() From bb59233d33fccd26f378dbdfdeb64b81e3cdc246 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 09:26:43 +0000 Subject: [PATCH 5/5] Trigger CI rebuild Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>