diff --git a/src/codeweaver/providers/config/profiles.py b/src/codeweaver/providers/config/profiles.py index 2570f197..5d658f0f 100644 --- a/src/codeweaver/providers/config/profiles.py +++ b/src/codeweaver/providers/config/profiles.py @@ -268,7 +268,9 @@ def _recommended_default( ), data=(TavilyProviderSettings(provider=Provider.TAVILY),) if Provider.TAVILY.has_env_auth and has_package("tavily") - else (DuckDuckGoProviderSettings(provider=Provider.DUCKDUCKGO),), + else (DuckDuckGoProviderSettings(provider=Provider.DUCKDUCKGO),) + if has_package("ddgs") + else (), vector_store=( QdrantVectorStoreProviderSettings( provider=Provider.QDRANT, diff --git a/tests/integration/chunker/config/test_client_factory_integration.py b/tests/integration/chunker/config/test_client_factory_integration.py index af73676f..31af9665 100644 --- a/tests/integration/chunker/config/test_client_factory_integration.py +++ b/tests/integration/chunker/config/test_client_factory_integration.py @@ -17,6 +17,16 @@ from codeweaver.core import Provider, ProviderCategory +def make_lazy_provider_mock(name: str, resolved_class: Mock, instance: Mock | None = None) -> Mock: + """Helper to create and configure a lazy provider mock.""" + lazy_mock = Mock() + lazy_mock.__name__ = name + lazy_mock._resolve.return_value = resolved_class + # mock_provider_lazy is invoked like the provider class + lazy_mock.return_value = instance if instance is not None else Mock() + return lazy_mock + + pytestmark = [ pytest.mark.integration, pytest.mark.skip(reason="ProviderRegistry removed - functionality tested through DI container"), @@ -75,13 +85,8 @@ def test_create_provider_with_client_from_map(self, registry): mock_provider_class = Mock() mock_provider_instance = Mock() - mock_provider_lazy = Mock() - mock_provider_lazy.__name__ = ( - "MockVoyageProvider" # Fix: Add __name__ to lazy mock (not resolved class) - ) - mock_provider_lazy._resolve.return_value = mock_provider_class - mock_provider_lazy.return_value = ( - mock_provider_instance # Fix: mock_provider_lazy is called at line 959 + mock_provider_lazy = make_lazy_provider_mock( + "MockVoyageProvider", mock_provider_class, mock_provider_instance ) mock_provider_class.return_value = mock_provider_instance @@ -124,12 +129,7 @@ def test_create_provider_skips_client_if_provided(self, registry): mock_lateimport._resolve.return_value = mock_client_class mock_provider_class = Mock() - mock_provider_lazy = Mock() - mock_provider_lazy.__name__ = ( - "MockVoyageProvider" # Fix: Add __name__ to lazy mock (not resolved class) - ) - mock_provider_lazy._resolve.return_value = mock_provider_class - mock_provider_lazy.return_value = Mock() # Fix: mock_provider_lazy is called at line 959 + mock_provider_lazy = make_lazy_provider_mock("MockVoyageProvider", mock_provider_class) mock_client_map = { Provider.VOYAGE: ( @@ -168,12 +168,7 @@ def test_create_provider_handles_client_creation_failure(self, registry): mock_lateimport._resolve.return_value = mock_client_class mock_provider_class = Mock(return_value=Mock()) - mock_provider_lazy = Mock() - mock_provider_lazy.__name__ = ( - "MockVoyageProvider" # Fix: Add __name__ to lazy mock (not resolved class) - ) - mock_provider_lazy._resolve.return_value = mock_provider_class - mock_provider_lazy.return_value = Mock() # Fix: mock_provider_lazy is called at line 959 + mock_provider_lazy = make_lazy_provider_mock("MockVoyageProvider", mock_provider_class) mock_client_map = { Provider.VOYAGE: ( @@ -246,9 +241,7 @@ def test_qdrant_provider_with_memory_mode(self, registry): mock_lateimport._resolve.return_value = mock_client_class mock_provider_class = Mock(return_value=Mock()) - mock_provider_lazy = Mock() - mock_provider_lazy._resolve.return_value = mock_provider_class - mock_provider_lazy.return_value = Mock() # Fix: mock_provider_lazy is what gets called + mock_provider_lazy = make_lazy_provider_mock("MockQdrantProvider", mock_provider_class) mock_client_map = { Provider.QDRANT: ( @@ -288,8 +281,7 @@ def test_qdrant_provider_with_url_mode(self, registry): mock_lateimport._resolve.return_value = mock_client_class mock_provider_class = Mock(return_value=Mock()) - mock_provider_lazy = Mock() - mock_provider_lazy._resolve.return_value = mock_provider_class + mock_provider_lazy = make_lazy_provider_mock("MockQdrantProvider", mock_provider_class) mock_client_map = { Provider.QDRANT: ( @@ -383,12 +375,7 @@ def test_string_provider_category_in_create_provider(self, registry): mock_lateimport._resolve.return_value = mock_client_class mock_provider_class = Mock(return_value=Mock()) - mock_provider_lazy = Mock() - mock_provider_lazy.__name__ = ( - "MockVoyageProvider" # Fix: Add __name__ to lazy mock (not resolved class) - ) - mock_provider_lazy._resolve.return_value = mock_provider_class - mock_provider_lazy.return_value = Mock() # Fix: mock_provider_lazy is called at line 959 + mock_provider_lazy = make_lazy_provider_mock("MockVoyageProvider", mock_provider_class) mock_client_map = { Provider.VOYAGE: ( diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py new file mode 100644 index 00000000..c2bf9661 --- /dev/null +++ b/tests/unit/test_main.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2025 Knitli Inc. +# SPDX-FileContributor: Adam Poulemanos +# +# SPDX-License-Identifier: MIT OR Apache-2.0 + +from unittest.mock import patch +import signal +import pytest + +from codeweaver.main import _setup_signal_handler + +def test_setup_signal_handler_first_interrupt(): + """Test that the first interrupt raises KeyboardInterrupt.""" + with patch("signal.signal") as mock_signal: + _setup_signal_handler() + + # Get the registered handler + mock_signal.assert_called_with(signal.SIGINT, mock_signal.call_args[0][1]) + force_shutdown_handler = mock_signal.call_args[0][1] + + # Test first call raises KeyboardInterrupt + with pytest.raises(KeyboardInterrupt): + force_shutdown_handler(signal.SIGINT, None) + +def test_setup_signal_handler_second_interrupt(): + """Test that the second interrupt exits immediately.""" + with patch("signal.signal") as mock_signal: + _setup_signal_handler() + + force_shutdown_handler = mock_signal.call_args[0][1] + + # First call raises KeyboardInterrupt + with pytest.raises(KeyboardInterrupt): + force_shutdown_handler(signal.SIGINT, None) + + # Second call exits with 1 + with patch("sys.exit") as mock_exit, patch("codeweaver.main.logger.warning") as mock_warning: + force_shutdown_handler(signal.SIGINT, None) + + mock_warning.assert_called_with("Force shutdown requested, exiting immediately...") + mock_exit.assert_called_with(1) + +def test_setup_signal_handler_suppress_errors(): + """Test that ValueError and OSError are suppressed when setting the signal.""" + # Test ValueError + with patch("signal.signal", side_effect=ValueError): + original_handler = _setup_signal_handler() + assert original_handler is None + + # Test OSError + with patch("signal.signal", side_effect=OSError): + original_handler = _setup_signal_handler() + assert original_handler is None