""" Tests for the candidate exploration system. Tests cover: - BaseCandidateExplorer search execution - Exploration time and candidate limits - Query generation and result handling """ import time from unittest.mock import Mock class TestBaseCandidateExplorer: """Tests for the BaseCandidateExplorer class.""" def test_base_explorer_execute_search_list_results(self): """Test _execute_search with list results.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) # Create a concrete implementation for testing class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() mock_search.run.return_value = [ {"title": "Result 1", "snippet": "Snippet 1"}, {"title": "Result 2", "snippet": "Snippet 2"}, ] explorer = TestExplorer( model=mock_model, search_engine=mock_search, max_candidates=50, max_search_time=60.0, ) result = explorer._execute_search("test query") assert "results" in result assert len(result["results"]) == 2 assert result["query"] == "test query" def test_base_explorer_execute_search_dict_results(self): """Test _execute_search with dict results.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() mock_search.run.return_value = { "results": [ {"title": "Result 1", "snippet": "Snippet 1"}, ], "total": 1, } explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) result = explorer._execute_search("test query") assert "results" in result assert len(result["results"]) == 1 def test_base_explorer_should_continue_time_limit(self): """Test exploration stops at time limit.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, max_candidates=100, max_search_time=1.0, # 1 second limit ) start_time = time.time() - 2.0 # Simulate 2 seconds elapsed result = explorer._should_continue_exploration(start_time, 0) assert result is False # Should stop due to time limit def test_base_explorer_should_continue_candidate_limit(self): """Test exploration stops at max candidates.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, max_candidates=10, max_search_time=60.0, ) start_time = time.time() # Just started result = explorer._should_continue_exploration(start_time, 15) assert result is False # Should stop due to candidate limit def test_base_explorer_deduplicate_candidates(self): """Test candidate deduplication.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) from local_deep_research.advanced_search_system.candidates.base_candidate import ( Candidate, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) candidates = [ Candidate(name="Apple Inc"), Candidate(name="apple inc"), # Duplicate (case insensitive) Candidate(name="Google"), Candidate(name=" google "), # Duplicate (with whitespace) Candidate(name="Microsoft"), ] unique = explorer._deduplicate_candidates(candidates) assert len(unique) == 3 # Only Apple, Google, Microsoft def test_base_explorer_rank_candidates_by_relevance(self): """Test candidate ranking.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) from local_deep_research.advanced_search_system.candidates.base_candidate import ( Candidate, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) candidates = [ Candidate( name="Random Thing", metadata={"query": "unrelated search"} ), Candidate( name="Python Language", metadata={"query": "python programming"} ), ] ranked = explorer._rank_candidates_by_relevance( candidates, "python programming" ) # The candidate with matching query words should rank higher assert ranked[0].name == "Python Language" def test_base_explorer_extract_entity_names_empty(self): """Test entity name extraction with empty text.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) # Empty text should return empty list names = explorer._extract_entity_names("") assert names == [] names = explorer._extract_entity_names(" ") assert names == [] def test_base_explorer_execute_search_unknown_format(self): """Test _execute_search with unknown result format.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() mock_search.run.return_value = "unexpected string result" explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) result = explorer._execute_search("test query") assert "results" in result assert result["results"] == [] def test_base_explorer_execute_search_exception(self): """Test _execute_search handles exceptions.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() mock_search.run.side_effect = Exception("Search error") explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) result = explorer._execute_search("test query") assert "results" in result assert result["results"] == [] def test_base_explorer_execute_search_tracks_queries(self): """Test _execute_search tracks explored queries.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() mock_search.run.return_value = [] explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) explorer._execute_search("Test Query") assert "test query" in explorer.explored_queries def test_base_explorer_extract_candidates_empty_results(self): """Test _extract_candidates_from_results with empty results.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) candidates = explorer._extract_candidates_from_results( {"results": []}, "test query" ) assert candidates == [] def test_base_explorer_extract_candidates_no_query(self): """Test _extract_candidates_from_results without original query.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) results = {"results": [{"title": "Test", "snippet": "Content"}]} candidates = explorer._extract_candidates_from_results(results) assert candidates == [] def test_base_explorer_extract_candidates_with_results(self): """Test _extract_candidates_from_results with actual results.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.return_value = Mock(content="Answer 1\nAnswer 2") mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) results = { "results": [ {"title": "Title 1", "snippet": "Snippet 1"}, {"title": "Title 2", "snippet": "Snippet 2"}, ], "query": "search query", } candidates = explorer._extract_candidates_from_results( results, "original query" ) assert len(candidates) == 2 assert candidates[0].name == "Answer 1" def test_base_explorer_extract_candidates_skips_duplicates(self): """Test _extract_candidates_from_results skips already found candidates.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) from local_deep_research.advanced_search_system.candidates.base_candidate import ( Candidate, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.return_value = Mock( content="Existing Answer\nNew Answer" ) mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) explorer.found_candidates["Existing Answer"] = Candidate( name="Existing Answer" ) results = {"results": [{"title": "Title", "snippet": "Snippet"}]} candidates = explorer._extract_candidates_from_results(results, "query") assert len(candidates) == 1 assert candidates[0].name == "New Answer" def test_base_explorer_generate_answer_candidates(self): """Test _generate_answer_candidates method.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.return_value = Mock( content="• Answer One\n- Answer Two\n1. Answer Three" ) mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) answers = explorer._generate_answer_candidates( "Question?", "Search content" ) assert len(answers) == 3 assert "Answer One" in answers assert "Answer Two" in answers assert "Answer Three" in answers def test_base_explorer_generate_answer_candidates_exception(self): """Test _generate_answer_candidates handles exceptions.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.side_effect = Exception("Model error") mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) answers = explorer._generate_answer_candidates("Question?", "Content") assert answers == [] def test_base_explorer_generate_answer_candidates_limits_to_five(self): """Test _generate_answer_candidates limits results to 5.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.return_value = Mock( content="A1\nA2\nA3\nA4\nA5\nA6\nA7\nA8" ) mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) answers = explorer._generate_answer_candidates("Q?", "Content") assert len(answers) <= 5 def test_base_explorer_generate_answer_candidates_skips_short(self): """Test _generate_answer_candidates skips very short answers.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.return_value = Mock(content="AB\nValid Answer\nXY") mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) answers = explorer._generate_answer_candidates("Q?", "Content") assert "AB" not in answers assert "XY" not in answers assert "Valid Answer" in answers def test_base_explorer_extract_entity_names_with_text(self): """Test _extract_entity_names with actual text.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.return_value = Mock(content="Apple Inc\nGoogle LLC") mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) names = explorer._extract_entity_names("Text about Apple and Google") assert len(names) == 2 assert "Apple Inc" in names def test_base_explorer_extract_entity_names_with_entity_type(self): """Test _extract_entity_names with entity type specified.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.return_value = Mock(content="New York\nLos Angeles") mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) explorer._extract_entity_names("Text about cities", "city") # Should have invoked model with entity type in prompt call_args = mock_model.invoke.call_args[0][0] assert "city" in call_args def test_base_explorer_extract_entity_names_exception(self): """Test _extract_entity_names handles exceptions.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.side_effect = Exception("Model error") mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) names = explorer._extract_entity_names("Some text") assert names == [] def test_base_explorer_extract_entity_names_filters_articles(self): """Test _extract_entity_names filters names starting with articles.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.return_value = Mock( content="The Company\na small thing\nan entity\nValid Name" ) mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) names = explorer._extract_entity_names("Text") assert "The Company" not in names assert "a small thing" not in names assert "an entity" not in names assert "Valid Name" in names def test_base_explorer_extract_entity_names_limits_to_five(self): """Test _extract_entity_names limits results to 5.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_model.invoke.return_value = Mock( content="Name1\nName2\nName3\nName4\nName5\nName6\nName7" ) mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) names = explorer._extract_entity_names("Text") assert len(names) <= 5 def test_base_explorer_should_continue_returns_true(self): """Test _should_continue_exploration returns True when within limits.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, max_candidates=100, max_search_time=60.0, ) start_time = time.time() result = explorer._should_continue_exploration(start_time, 5) assert result is True def test_base_explorer_rank_empty_candidates(self): """Test _rank_candidates_by_relevance with empty list.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) result = explorer._rank_candidates_by_relevance([], "query") assert result == [] def test_base_explorer_rank_with_result_title(self): """Test _rank_candidates_by_relevance with result_title metadata.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) from local_deep_research.advanced_search_system.candidates.base_candidate import ( Candidate, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) candidates = [ Candidate( name="Low Match", metadata={ "query": "unrelated", "result_title": "something else", }, ), Candidate( name="High Match", metadata={ "query": "python programming", "result_title": "python programming guide", }, ), ] ranked = explorer._rank_candidates_by_relevance( candidates, "python programming" ) assert ranked[0].name == "High Match" def test_base_explorer_init_defaults(self): """Test BaseCandidateExplorer initialization defaults.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( BaseCandidateExplorer, ) class TestExplorer(BaseCandidateExplorer): def explore( self, initial_query, constraints=None, entity_type=None ): pass def generate_exploration_queries( self, base_query, found_candidates, constraints=None ): return [] mock_model = Mock() mock_search = Mock() explorer = TestExplorer( model=mock_model, search_engine=mock_search, ) assert explorer.max_candidates == 50 assert explorer.max_search_time == 60.0 assert explorer.explored_queries == set() assert explorer.found_candidates == {} def test_exploration_strategy_enum(self): """Test ExplorationStrategy enum values.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( ExplorationStrategy, ) assert ExplorationStrategy.BREADTH_FIRST.value == "breadth_first" assert ExplorationStrategy.DEPTH_FIRST.value == "depth_first" assert ( ExplorationStrategy.CONSTRAINT_GUIDED.value == "constraint_guided" ) assert ( ExplorationStrategy.DIVERSITY_FOCUSED.value == "diversity_focused" ) assert ExplorationStrategy.ADAPTIVE.value == "adaptive" def test_exploration_result_dataclass(self): """Test ExplorationResult dataclass.""" from local_deep_research.advanced_search_system.candidate_exploration.base_explorer import ( ExplorationResult, ExplorationStrategy, ) from local_deep_research.advanced_search_system.candidates.base_candidate import ( Candidate, ) candidates = [Candidate(name="Test")] result = ExplorationResult( candidates=candidates, total_searched=10, unique_candidates=5, exploration_paths=["path1", "path2"], metadata={"key": "value"}, elapsed_time=5.5, strategy_used=ExplorationStrategy.BREADTH_FIRST, ) assert len(result.candidates) == 1 assert result.total_searched == 10 assert result.unique_candidates == 5 assert len(result.exploration_paths) == 2 assert result.metadata["key"] == "value" assert result.elapsed_time == 5.5 assert result.strategy_used == ExplorationStrategy.BREADTH_FIRST