""" Dynamic module mocking utilities - inspired by scottvr's approach. This module provides utilities for creating mock modules dynamically during tests. """ import sys import types from typing import Any, Dict, Optional from unittest.mock import Mock def create_mock_module( module_name: str, attributes: Dict[str, Any] ) -> types.ModuleType: """ Create a mock module with specified attributes. Args: module_name: Name of the module to create attributes: Dictionary of attributes to add to the module Returns: Mock module instance """ mock_module = types.ModuleType(module_name) for key, value in attributes.items(): setattr(mock_module, key, value) return mock_module def patch_module(monkeypatch, module_path: str, mock_module: types.ModuleType): """ Patch a module in the import system. Args: monkeypatch: pytest monkeypatch fixture module_path: Full path to the module (e.g., 'local_deep_research.config.llm_config') mock_module: The mock module to use """ monkeypatch.setitem(sys.modules, module_path, mock_module) # Note: We only use sys.modules patching. The previous setattr approach # could fail when the parent module doesn't have the attribute yet, # causing AttributeError in parallel test execution. def create_mock_llm_config(monkeypatch): """ Create and patch a complete mock llm_config module. This is useful for testing components that depend on llm_config without requiring actual LLM connections. """ def get_llm(*args, **kwargs): mock = Mock() mock.invoke.return_value = Mock(content="Mocked LLM response") return mock attributes = { "get_llm": get_llm, "DEFAULT_PROVIDER": "ollama", "DEFAULT_MODEL": "gemma3:12b", "DEFAULT_TEMPERATURE": 0.7, "DEFAULT_MAX_TOKENS": 4096, } mock_module = create_mock_module("llm_config", attributes) patch_module( monkeypatch, "local_deep_research.config.llm_config", mock_module ) return mock_module def create_mock_search_config(monkeypatch): """ Create and patch a complete mock search_config module. """ def get_search(search_tool: Optional[str] = None, **kwargs): mock = Mock() mock.run.return_value = [ { "title": "Mock Search Result", "link": "https://example.com/mock", "snippet": "This is a mock search result", } ] return mock def get_available_search_tools(): return { "searxng": "SearXNG Meta Search", "ddg": "DuckDuckGo", "google_pse": "Google Programmable Search Engine", "none": "No search (testing)", } attributes = { "get_search": get_search, "get_available_search_tools": get_available_search_tools, "AVAILABLE_SEARCH_TOOLS": get_available_search_tools(), "DEFAULT_SEARCH_TOOL": "searxng", "DEFAULT_MAX_RESULTS": 50, } mock_module = create_mock_module("search_config", attributes) patch_module( monkeypatch, "local_deep_research.config.search_config", mock_module ) return mock_module def create_mock_db_utils( monkeypatch, settings: Optional[Dict[str, Any]] = None ): """ Create and patch a mock db_utils module with configurable settings. """ default_settings = { "general.enable_fact_checking": True, "llm.provider": "ollama", "llm.model": "gemma3:12b", "search.tool": "searxng", "search.iterations": 3, } if settings: default_settings.update(settings) # get_setting_from_db_main_thread has been removed from the codebase def get_db_session(): return Mock() def get_settings_manager(): mock_manager = Mock() mock_manager.get.side_effect = lambda k, d=None: default_settings.get( k, d ) return mock_manager attributes = { # get_setting_from_db_main_thread has been removed "get_db_session": get_db_session, "get_settings_manager": get_settings_manager, # Add cache_clear methods for compatibility "get_db_session.cache_clear": lambda: None, "get_settings_manager.cache_clear": lambda: None, } mock_module = create_mock_module("db_utils", attributes) patch_module( monkeypatch, "local_deep_research.utilities.db_utils", mock_module ) return mock_module