fix: merge main, address code review feedback for security fix PR #816

- Merge origin/main into branch (resolve conflict in integrations_dashboard.html)
- Add defensive JSON parsing with try/except for integration.config
- Wrap tester() call in try/except to prevent 500 errors from bad config
- Add i18n key integrations.connection_test_failed_fallback in en.json
- Reference i18n key in template JS fallback message
- Update SECURITY_AUDIT.md: add fix date (2026-03-23), update doc date
- Remove accidental revert.sh file
- Fix missing MagicMock/patch imports in test file
- Add tests for invalid JSON config and tester exception error paths

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/daebb70e-059a-4601-8864-88eef49f99cf
This commit is contained in:
copilot-swe-agent[bot]
2026-03-23 16:21:09 +00:00
parent 41d6f682c0
commit 1a195a96bd
359 changed files with 139902 additions and 2252 deletions
+152 -4
View File
@@ -69,8 +69,9 @@ class TestViewsBase:
context = {"request": req}
template_response_with_version("template.html", context)
args, _ = mock_orig.call_args
assert args[1].get("csrf_token") == "my-csrf"
args, kwargs = mock_orig.call_args
context = kwargs.get("context", {})
assert context.get("csrf_token") == "my-csrf"
def test_kwargs_context_no_request(self):
"""Test kwargs context path when request is not in context."""
@@ -704,6 +705,18 @@ def _set_minimal_provider_settings(mock_settings):
class TestSettingsSyncAdditional:
"""Additional tests for settings_sync covering reload failure branch."""
def test_notify_settings_updated_redis_failure_logs_warning(self):
"""Test that a Redis failure is logged, not raised (lines 58-59)."""
from app.utils.settings_sync import notify_settings_updated
with patch("app.utils.settings_sync.redis") as mock_redis_module:
mock_redis_module.from_url.side_effect = Exception("Redis connection failed")
with patch("app.utils.settings_sync.logger") as mock_logger:
notify_settings_updated()
mock_logger.warning.assert_any_call(
"Could not publish settings update to Redis: Redis connection failed"
)
def test_reload_failure_is_logged_not_raised(self):
"""Test that a reload failure is logged, not raised (lines 71-72)."""
from app.utils.settings_sync import notify_settings_updated
@@ -711,8 +724,66 @@ class TestSettingsSyncAdditional:
with patch("app.utils.settings_sync.redis") as mock_redis_module:
mock_redis_module.from_url.return_value = MagicMock() # Redis OK
with patch("app.utils.config_loader.reload_settings_from_db", side_effect=Exception("reload failed")):
# Should not raise despite reload failure
notify_settings_updated()
with patch("app.utils.settings_sync.logger") as mock_logger:
# Should not raise despite reload failure
notify_settings_updated()
mock_logger.warning.assert_any_call("Could not reload in-process settings: reload failed")
def test_notify_settings_updated_ocr_failure_logs_warning(self):
"""Test that OCR check failure is logged, not raised (lines 82-83)."""
from app.utils.settings_sync import notify_settings_updated
with patch("app.utils.settings_sync.redis") as mock_redis_module:
mock_redis_module.from_url.return_value = MagicMock() # Redis OK
with patch("app.utils.config_loader.reload_settings_from_db"):
with patch(
"app.utils.ocr_language_manager.ensure_ocr_languages_async", side_effect=Exception("OCR failed")
):
with patch("app.utils.settings_sync.logger") as mock_logger:
notify_settings_updated()
mock_logger.warning.assert_any_call("Could not schedule OCR language check: OCR failed")
def test_signal_handler_ocr_check_failure_logs_warning(self):
"""Test that signal handler logs warning if OCR language check fails on worker (lines 115-116)."""
from app.utils.settings_sync import register_settings_reload_signal
handler_fn = None
def capture_connect(fn=None, weak=None, **kwargs):
nonlocal handler_fn
if fn is not None:
handler_fn = fn
return fn
def decorator(func):
nonlocal handler_fn
handler_fn = func
return func
return decorator
with patch("app.utils.settings_sync.task_prerun") as mock_signal:
mock_signal.connect = capture_connect
register_settings_reload_signal()
assert handler_fn is not None
mock_redis = MagicMock()
mock_redis.get.return_value = b"1234567890.0"
with patch("app.utils.settings_sync.redis") as mock_redis_mod:
mock_redis_mod.from_url.return_value = mock_redis
with patch("app.utils.config_loader.reload_settings_from_db"):
with patch("app.utils.settings_sync._last_seen_version", ""):
with patch(
"app.utils.ocr_language_manager.ensure_ocr_languages_async",
side_effect=Exception("Worker OCR fail"),
):
with patch("app.utils.settings_sync.logger") as mock_logger:
handler_fn(sender=None)
mock_logger.warning.assert_any_call(
"Could not schedule OCR language check on worker: Worker OCR fail"
)
def test_signal_handler_reloads_on_version_change(self):
"""Test the task_prerun signal handler reloads settings when version changes (lines 95-98)."""
@@ -820,6 +891,83 @@ class TestSettingsSyncAdditional:
# Should not raise
handler_fn(sender=None)
def test_signal_handler_no_version_returned(self):
"""Test that handler does nothing if Redis returns None for version."""
from app.utils.settings_sync import register_settings_reload_signal
handler_fn = None
def capture_connect(fn=None, weak=None, **kwargs):
nonlocal handler_fn
if fn is not None:
handler_fn = fn
return fn
def decorator(func):
nonlocal handler_fn
handler_fn = func
return func
return decorator
with patch("app.utils.settings_sync.task_prerun") as mock_signal:
mock_signal.connect = capture_connect
register_settings_reload_signal()
assert handler_fn is not None
mock_redis = MagicMock()
mock_redis.get.return_value = None # Return None for version
with patch("app.utils.settings_sync.redis") as mock_redis_mod:
mock_redis_mod.from_url.return_value = mock_redis
with patch("app.utils.config_loader.reload_settings_from_db") as mock_reload:
handler_fn(sender=None)
mock_reload.assert_not_called()
def test_signal_handler_ocr_language_manager_exception(self):
"""Test that OCR language check exception inside handler is caught and logged."""
from app.utils.settings_sync import register_settings_reload_signal
handler_fn = None
def capture_connect(fn=None, weak=None, **kwargs):
nonlocal handler_fn
if fn is not None:
handler_fn = fn
return fn
def decorator(func):
nonlocal handler_fn
handler_fn = func
return func
return decorator
with patch("app.utils.settings_sync.task_prerun") as mock_signal:
mock_signal.connect = capture_connect
register_settings_reload_signal()
assert handler_fn is not None
mock_redis = MagicMock()
mock_redis.get.return_value = b"9999999.0" # New version
with patch("app.utils.settings_sync.redis") as mock_redis_mod:
mock_redis_mod.from_url.return_value = mock_redis
with patch("app.utils.config_loader.reload_settings_from_db") as mock_reload:
with patch("app.utils.settings_sync._last_seen_version", "111.0"):
with patch("app.utils.settings_sync.logger") as mock_logger:
with patch(
"app.utils.ocr_language_manager.ensure_ocr_languages_async",
side_effect=Exception("OCR failed"),
):
handler_fn(sender=None)
mock_reload.assert_called_once()
mock_logger.warning.assert_called_with(
"Could not schedule OCR language check on worker: OCR failed"
)
# ===========================================================================
# app/api/logs.py additional branches