""" Deep coverage tests for settings_routes.py targeting ~170 remaining uncovered statements. Uncovered functions/branches targeted: - _get_setting_from_session: db_session is None - save_all_settings: corrupted llm.provider, bracket chars, unknown prefix, single-update non-bool message, key not in original_values, set_setting fails, new setting UI detection (bool/int/dict), skip empty keys, validation error - save_settings: commit failure rollback, failed_count flash, non-editable POST filter, setting exception in loop - api_get_all_settings: exception path - api_get_db_setting: exception path - api_update_setting: exception path - api_delete_setting: exception path - api_import_settings: exception path - api_get_categories: exception path - api_get_search_favorites: non-list reset, exception path - api_update_search_favorites: exception path - api_toggle_search_favorite: exception path - fix_corrupted_settings: llm.temperature, llm.max_tokens, search sub-keys, report.searches_per_section, app sub-keys, report unknown key fallback, empty dict corruption - inject_csrf_token: context processor - save_all_settings: database/app prefix categorization """ from contextlib import contextmanager from unittest.mock import MagicMock, patch from ._settings_route_helpers import ( _authenticated_client, _create_test_app, _make_setting, ) # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- MODULE = "local_deep_research.web.routes.settings_routes" DECORATOR_MODULE = "local_deep_research.web.utils.route_decorators" SETTINGS_PREFIX = "/settings" # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- class TestGetSettingFromSession: """_get_setting_from_session: db_session is None branch.""" def test_returns_default_when_db_session_is_none(self): from local_deep_research.web.routes.settings_routes import ( _get_setting_from_session, ) @contextmanager def _fake_ctx(*a, **kw): yield None app = _create_test_app() with app.test_request_context(): from flask import session session["username"] = "testuser" with patch(f"{MODULE}.get_user_db_session", side_effect=_fake_ctx): result = _get_setting_from_session("any.key", "fallback") assert result == "fallback" class TestInjectCsrfToken: """Test the inject_csrf_token context processor.""" def test_injects_callable(self): from local_deep_research.web.routes.settings_routes import ( inject_csrf_token, ) result = inject_csrf_token() assert "csrf_token" in result assert callable(result["csrf_token"]) class TestSaveAllSettingsNewSettingUIDetection: """save_all_settings: new setting creation with different value types.""" @patch(f"{MODULE}.create_or_update_setting") def test_new_bool_setting_gets_checkbox(self, mock_create): mock_new = _make_setting(key="app.flag", value=True) mock_new.type = "app" mock_create.return_value = mock_new app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [[], []] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"app.flag": True}, ) assert resp.status_code == 200 call_args = mock_create.call_args[0][0] assert call_args["ui_element"] == "checkbox" @patch(f"{MODULE}.create_or_update_setting") def test_new_int_setting_gets_number(self, mock_create): mock_new = _make_setting(key="app.count", value=42) mock_new.type = "app" mock_create.return_value = mock_new app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [[], []] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"app.count": 42}, ) assert resp.status_code == 200 call_args = mock_create.call_args[0][0] assert call_args["ui_element"] == "number" @patch(f"{MODULE}.create_or_update_setting") def test_new_dict_setting_gets_textarea(self, mock_create): mock_new = _make_setting(key="report.structure", value={}) mock_new.type = "report" mock_create.return_value = mock_new app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [[], []] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"report.structure": {"a": 1}}, ) assert resp.status_code == 200 call_args = mock_create.call_args[0][0] assert call_args["ui_element"] == "textarea" @patch(f"{MODULE}.create_or_update_setting") def test_unknown_prefix_rejected_with_validation_error(self, mock_create): """Unknown prefix is rejected by the namespace gate with 400.""" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [[], []] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"custom.param": "value"}, ) # Namespace gate rejects the key; response has status=error with 400 assert resp.status_code == 400 data = resp.get_json() assert any( e["key"] == "custom.param" and "not allowed" in e["error"] for e in data["errors"] ) mock_create.assert_not_called() class TestSaveAllSettingsSkipInvalidKeys: """save_all_settings: empty string keys are skipped.""" @patch(f"{MODULE}.set_setting", return_value=True) @patch( f"{MODULE}.coerce_setting_for_write", side_effect=lambda key, value, ui_element: value, ) @patch(f"{MODULE}.validate_setting", return_value=(True, None)) def test_empty_key_skipped(self, mock_v, mock_c, mock_s): setting = _make_setting(key="llm.model", value="gpt-4", editable=True) setting.type = "llm" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [ [setting], [setting], ] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"": "ignored", "llm.model": "gpt-4"}, ) assert resp.status_code == 200 assert mock_s.call_count == 1 class TestSaveAllSettingsCorruptedBranches: """save_all_settings: additional corrupted value correction branches.""" @patch(f"{MODULE}.set_setting", return_value=True) @patch( f"{MODULE}.coerce_setting_for_write", side_effect=lambda key, value, ui_element: value, ) @patch(f"{MODULE}.validate_setting", return_value=(True, None)) def test_corrupted_llm_provider(self, mock_v, mock_c, mock_s): setting = _make_setting( key="llm.provider", value="openai", ui_element="text", editable=True ) setting.type = "llm" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [ [setting], [setting], ] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"llm.provider": "[object Object]"}, ) assert resp.status_code == 200 @patch(f"{MODULE}.set_setting", return_value=True) @patch( f"{MODULE}.coerce_setting_for_write", side_effect=lambda key, value, ui_element: value, ) @patch(f"{MODULE}.validate_setting", return_value=(True, None)) def test_corrupted_unknown_key_becomes_none(self, mock_v, mock_c, mock_s): setting = _make_setting( key="database.name", value="test", ui_element="text", editable=True ) setting.type = "database" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [ [setting], [setting], ] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"database.name": "[object Object]"}, ) assert resp.status_code == 200 @patch(f"{MODULE}.set_setting", return_value=True) @patch( f"{MODULE}.coerce_setting_for_write", side_effect=lambda key, value, ui_element: value, ) @patch(f"{MODULE}.validate_setting", return_value=(True, None)) def test_corrupted_bracket_char(self, mock_v, mock_c, mock_s): """Value '{' (single bracket) is detected as corrupted.""" setting = _make_setting( key="llm.model", value="gpt-4", ui_element="text", editable=True ) setting.type = "llm" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [ [setting], [setting], ] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"llm.model": "{"}, ) assert resp.status_code == 200 class TestSaveAllSettingsSuccessMessages: """save_all_settings: different success message formats.""" @patch(f"{MODULE}.set_setting", return_value=True) @patch( f"{MODULE}.coerce_setting_for_write", side_effect=lambda key, value, ui_element: value, ) @patch(f"{MODULE}.validate_setting", return_value=(True, None)) def test_single_bool_enabled_message(self, mock_v, mock_c, mock_s): setting = _make_setting( key="app.dark_mode", value=True, ui_element="checkbox", editable=True, ) setting.type = "app" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [ [setting], [setting], ] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"app.dark_mode": True}, ) assert resp.status_code == 200 data = resp.get_json() assert "enabled" in data["message"] or "disabled" in data["message"] @patch(f"{MODULE}.set_setting", return_value=True) @patch( f"{MODULE}.coerce_setting_for_write", side_effect=lambda key, value, ui_element: value, ) @patch(f"{MODULE}.validate_setting", return_value=(True, None)) def test_single_non_bool_updated_message(self, mock_v, mock_c, mock_s): setting = _make_setting( key="llm.temperature", value=0.7, ui_element="number", editable=True ) setting.type = "llm" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [ [setting], [setting], ] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"llm.temperature": 0.5}, ) assert resp.status_code == 200 data = resp.get_json() assert "updated" in data["message"] class TestSaveAllSettingsValidationError: """save_all_settings: validation failure returns 400.""" @patch(f"{MODULE}.coerce_setting_for_write", return_value=-1) @patch( f"{MODULE}.validate_setting", return_value=(False, "Value must be at least 0"), ) def test_validation_error_on_existing(self, mock_v, mock_c): setting = _make_setting( key="search.iterations", value=3, ui_element="number", editable=True, name="Iterations", ) setting.type = "search" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [ [setting], [setting], ] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"search.iterations": -1}, ) assert resp.status_code == 400 data = resp.get_json() assert data["status"] == "error" assert len(data["errors"]) == 1 class TestSaveSettingsFormPost: """save_settings: traditional POST edge cases.""" def test_commit_failure_rollback(self): setting = _make_setting( key="llm.model", value="gpt-4", ui_element="text", editable=True ) app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.return_value = [setting] mock_session.commit.side_effect = RuntimeError("commit failed") mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) with patch( f"{DECORATOR_MODULE}.SettingsManager" ) as mock_sm_cls: mock_sm = MagicMock() mock_sm.set_setting.return_value = True mock_sm_cls.return_value = mock_sm with patch( f"{MODULE}.coerce_setting_for_write", return_value="gpt-4", ): resp = client.post( f"{SETTINGS_PREFIX}/save_settings", data={"llm.model": "gpt-4"}, ) assert resp.status_code == 302 mock_session.rollback.assert_called_once() def test_set_setting_returns_false(self): setting = _make_setting( key="llm.model", value="gpt-4", ui_element="text", editable=True ) app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.return_value = [setting] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) with patch( f"{DECORATOR_MODULE}.SettingsManager" ) as mock_sm_cls: mock_sm = MagicMock() mock_sm.set_setting.return_value = False mock_sm_cls.return_value = mock_sm with patch( f"{MODULE}.coerce_setting_for_write", return_value="gpt-4", ): resp = client.post( f"{SETTINGS_PREFIX}/save_settings", data={"llm.model": "gpt-4"}, ) assert resp.status_code == 302 def test_setting_exception_in_loop(self): setting = _make_setting( key="llm.model", value="gpt-4", ui_element="text", editable=True ) app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.return_value = [setting] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) with patch( f"{DECORATOR_MODULE}.SettingsManager" ) as mock_sm_cls: mock_sm = MagicMock() mock_sm.set_setting.side_effect = RuntimeError("unexpected") mock_sm_cls.return_value = mock_sm with patch( f"{MODULE}.coerce_setting_for_write", return_value="gpt-4", ): resp = client.post( f"{SETTINGS_PREFIX}/save_settings", data={"llm.model": "gpt-4"}, ) assert resp.status_code == 302 def test_non_editable_skipped(self): setting = _make_setting( key="app.locked", value="v", ui_element="text", editable=False ) app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.return_value = [setting] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) with patch( f"{DECORATOR_MODULE}.SettingsManager" ) as mock_sm_cls: mock_sm = MagicMock() mock_sm.set_setting.return_value = True mock_sm_cls.return_value = mock_sm resp = client.post( f"{SETTINGS_PREFIX}/save_settings", data={"app.locked": "new_val"}, ) assert resp.status_code == 302 class TestApiGetAllSettingsException: """api_get_all_settings: exception -> 500.""" def test_exception_returns_500(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_ctx.side_effect = RuntimeError("db fail") resp = client.get(f"{SETTINGS_PREFIX}/api") assert resp.status_code == 500 assert "error" in resp.get_json() class TestApiGetDbSettingException: """api_get_db_setting: exception -> 500.""" def test_exception_returns_500(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_ctx.side_effect = RuntimeError("db fail") resp = client.get(f"{SETTINGS_PREFIX}/api/llm.model") assert resp.status_code == 500 class TestApiUpdateSettingException: """api_update_setting: unhandled exception -> 500.""" def test_exception_returns_500(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_ctx.side_effect = RuntimeError("db fail") resp = client.put( f"{SETTINGS_PREFIX}/api/llm.model", json={"value": "test"}, ) assert resp.status_code == 500 assert "error" in resp.get_json() class TestApiDeleteSettingException: """api_delete_setting: unhandled exception -> 500.""" def test_exception_returns_500(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_ctx.side_effect = RuntimeError("db fail") resp = client.delete(f"{SETTINGS_PREFIX}/api/llm.model") assert resp.status_code == 500 assert "error" in resp.get_json() class TestApiGetCategoriesException: """api_get_categories: exception -> 500.""" def test_exception_returns_500(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_ctx.side_effect = RuntimeError("db fail") resp = client.get(f"{SETTINGS_PREFIX}/api/categories") assert resp.status_code == 500 class TestApiImportSettingsException: """api_import_settings: exception -> 500.""" def test_exception_returns_500(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_ctx.side_effect = RuntimeError("db fail") resp = client.post(f"{SETTINGS_PREFIX}/api/import") assert resp.status_code == 500 class TestApiGetSearchFavoritesEdge: """api_get_search_favorites: edge cases.""" def test_non_list_favorites_reset(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) with patch( f"{DECORATOR_MODULE}.SettingsManager" ) as mock_sm_cls: mock_sm = MagicMock() mock_sm.get_setting.return_value = "not_a_list" mock_sm_cls.return_value = mock_sm resp = client.get(f"{SETTINGS_PREFIX}/api/search-favorites") assert resp.status_code == 200 assert resp.get_json()["favorites"] == [] def test_exception_returns_500(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_ctx.side_effect = RuntimeError("fail") resp = client.get(f"{SETTINGS_PREFIX}/api/search-favorites") assert resp.status_code == 500 class TestApiUpdateSearchFavoritesException: """api_update_search_favorites: exception -> 500.""" def test_exception_returns_500(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_ctx.side_effect = RuntimeError("fail") resp = client.put( f"{SETTINGS_PREFIX}/api/search-favorites", json={"favorites": ["google"]}, ) assert resp.status_code == 500 class TestApiToggleSearchFavoriteException: """api_toggle_search_favorite: exception -> 500.""" def test_exception_returns_500(self): app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_ctx.side_effect = RuntimeError("fail") resp = client.post( f"{SETTINGS_PREFIX}/api/search-favorites/toggle", json={"engine_id": "google"}, ) assert resp.status_code == 500 class TestFixCorruptedSettingsSubKeys: """fix_corrupted_settings: additional corrupted key defaults.""" def _post_fix(self, client, settings_list): with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.group_by.return_value.having.return_value.all.return_value = [] mock_session.query.return_value.all.return_value = settings_list mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) return client.post(f"{SETTINGS_PREFIX}/fix_corrupted_settings") def test_fixes_search_sub_keys(self): settings = [ _make_setting(key="search.questions_per_iteration", value=None), _make_setting(key="search.searches_per_section", value="null"), _make_setting( key="search.skip_relevance_filter", value="undefined" ), _make_setting(key="search.safe_search", value="{}"), _make_setting(key="search.search_language", value=None), ] app = _create_test_app() with _authenticated_client(app) as client: resp = self._post_fix(client, settings) assert resp.status_code == 200 data = resp.get_json() assert "search.questions_per_iteration" in data["fixed_settings"] assert "search.safe_search" in data["fixed_settings"] def test_fixes_app_enable_notifications(self): settings = [_make_setting(key="app.enable_notifications", value="null")] app = _create_test_app() with _authenticated_client(app) as client: resp = self._post_fix(client, settings) assert resp.status_code == 200 assert "app.enable_notifications" in resp.get_json()["fixed_settings"] def test_fixes_llm_temperature_and_max_tokens(self): settings = [ _make_setting(key="llm.temperature", value=None), _make_setting(key="llm.max_tokens", value="undefined"), ] app = _create_test_app() with _authenticated_client(app) as client: resp = self._post_fix(client, settings) assert resp.status_code == 200 data = resp.get_json() assert "llm.temperature" in data["fixed_settings"] assert "llm.max_tokens" in data["fixed_settings"] def test_report_unknown_key_fallback(self): settings = [ _make_setting(key="report.unknown_key", value="[object Object]") ] app = _create_test_app() with _authenticated_client(app) as client: resp = self._post_fix(client, settings) assert resp.status_code == 200 assert "report.unknown_key" in resp.get_json()["fixed_settings"] def test_empty_dict_corruption(self): settings = [_make_setting(key="llm.provider", value={})] app = _create_test_app() with _authenticated_client(app) as client: resp = self._post_fix(client, settings) assert resp.status_code == 200 assert "llm.provider" in resp.get_json()["fixed_settings"] def test_report_searches_per_section(self): settings = [ _make_setting(key="report.searches_per_section", value="null") ] app = _create_test_app() with _authenticated_client(app) as client: resp = self._post_fix(client, settings) assert resp.status_code == 200 assert ( "report.searches_per_section" in resp.get_json()["fixed_settings"] ) class TestSaveAllSettingsTypeCategorization: """save_all_settings: setting type categorization for various prefixes.""" @patch(f"{MODULE}.set_setting", return_value=True) @patch( f"{MODULE}.coerce_setting_for_write", side_effect=lambda key, value, ui_element: value, ) @patch(f"{MODULE}.validate_setting", return_value=(True, None)) def test_database_prefix(self, mock_v, mock_c, mock_s): setting = _make_setting( key="database.path", value="/tmp", ui_element="text", editable=True ) setting.type = "database" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [ [setting], [setting], ] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"database.path": "/new"}, ) assert resp.status_code == 200 @patch(f"{MODULE}.set_setting", return_value=True) @patch( f"{MODULE}.coerce_setting_for_write", side_effect=lambda key, value, ui_element: value, ) @patch(f"{MODULE}.validate_setting", return_value=(True, None)) def test_llm_parameters_category(self, mock_v, mock_c, mock_s): """llm.temperature -> category=llm_parameters.""" setting = _make_setting( key="llm.temperature", value=0.7, ui_element="number", editable=True ) setting.type = "llm" app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{DECORATOR_MODULE}.get_user_db_session") as mock_ctx: mock_session = MagicMock() mock_session.query.return_value.all.side_effect = [ [setting], [setting], ] mock_ctx.return_value.__enter__ = MagicMock( return_value=mock_session ) mock_ctx.return_value.__exit__ = MagicMock(return_value=False) resp = client.post( f"{SETTINGS_PREFIX}/save_all_settings", json={"llm.temperature": 0.9}, ) assert resp.status_code == 200 class TestGetBulkSettingsOuterException: """get_bulk_settings: outer exception -> 500.""" def test_outer_exception(self): # Patch jsonify to raise on the first call (the success-path return), # which is outside the inner per-key try/except and will be caught by # the outer except block, returning a 500. We cannot patch the module- # level `request` proxy directly because it requires an active request # context at patch-start time. real_jsonify = __import__("flask", fromlist=["jsonify"]).jsonify call_count = {"n": 0} def _raise_first(*args, **kwargs): call_count["n"] += 1 if call_count["n"] == 1: raise RuntimeError("boom") return real_jsonify(*args, **kwargs) app = _create_test_app() with _authenticated_client(app) as client: with patch(f"{MODULE}.jsonify", side_effect=_raise_first): resp = client.get(f"{SETTINGS_PREFIX}/api/bulk") assert resp.status_code == 500