diff --git a/services/supabase/coverages/test_get_coverages.py b/services/supabase/coverages/test_get_coverages.py index cd876f9bb..b3ea9f7c7 100644 --- a/services/supabase/coverages/test_get_coverages.py +++ b/services/supabase/coverages/test_get_coverages.py @@ -7,7 +7,6 @@ # Third-party imports import pytest - # Local imports from services.supabase.client import supabase from services.supabase.coverages.get_coverages import get_coverages @@ -93,7 +92,6 @@ def test_get_coverages_success_with_data( self, mock_supabase_chain, sample_coverage_data ): """Test successful coverage retrieval when data exists.""" - # Setup mock_result = Mock() mock_result.data = sample_coverage_data mock_supabase_chain.execute.return_value = mock_result @@ -101,10 +99,8 @@ def test_get_coverages_success_with_data( repo_id = 123 filenames = ["src/main.py", "src/utils.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert isinstance(result, dict) assert len(result) == 2 assert "src/main.py" in result @@ -112,7 +108,6 @@ def test_get_coverages_success_with_data( assert result["src/main.py"]["line_coverage"] == 85.5 assert result["src/utils.py"]["line_coverage"] == 95.0 - # Verify database query was constructed correctly mock_supabase_chain.select.assert_called_once_with("*") mock_supabase_chain.eq.assert_called_once_with("repo_id", repo_id) mock_supabase_chain.in_.assert_called_once_with("full_path", filenames) @@ -120,22 +115,16 @@ def test_get_coverages_success_with_data( def test_get_coverages_empty_filenames_list(self, mock_supabase_chain): """Test that empty filenames list returns empty dictionary without database query.""" - # Setup repo_id = 123 filenames = [] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert not result - - # Verify no database query was made mock_supabase_chain.execute.assert_not_called() def test_get_coverages_no_data_found(self, mock_supabase_chain): """Test when no coverage data is found in the database.""" - # Setup mock_result = Mock() mock_result.data = [] mock_supabase_chain.execute.return_value = mock_result @@ -143,16 +132,13 @@ def test_get_coverages_no_data_found(self, mock_supabase_chain): repo_id = 123 filenames = ["src/nonexistent.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert not result mock_supabase_chain.execute.assert_called_once() def test_get_coverages_none_data(self, mock_supabase_chain): """Test when result.data is None.""" - # Setup mock_result = Mock() mock_result.data = None mock_supabase_chain.execute.return_value = mock_result @@ -160,16 +146,13 @@ def test_get_coverages_none_data(self, mock_supabase_chain): repo_id = 123 filenames = ["src/test.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert not result mock_supabase_chain.execute.assert_called_once() def test_get_coverages_single_file(self, mock_supabase_chain, sample_coverage_data): """Test coverage retrieval for a single file.""" - # Setup single_file_data = [sample_coverage_data[0]] mock_result = Mock() mock_result.data = single_file_data @@ -178,10 +161,8 @@ def test_get_coverages_single_file(self, mock_supabase_chain, sample_coverage_da repo_id = 123 filenames = ["src/main.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert len(result) == 1 assert "src/main.py" in result assert result["src/main.py"]["id"] == 1 @@ -191,7 +172,6 @@ def test_get_coverages_multiple_files( self, mock_supabase_chain, sample_coverage_data ): """Test coverage retrieval for multiple files.""" - # Setup mock_result = Mock() mock_result.data = sample_coverage_data mock_supabase_chain.execute.return_value = mock_result @@ -199,11 +179,9 @@ def test_get_coverages_multiple_files( repo_id = 123 filenames = ["src/main.py", "src/utils.py", "src/nonexistent.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify - assert len(result) == 2 # Only files with data are returned + assert len(result) == 2 assert "src/main.py" in result assert "src/utils.py" in result assert "src/nonexistent.py" not in result @@ -212,7 +190,6 @@ def test_get_coverages_with_different_repo_id( self, mock_supabase_chain, sample_coverage_data ): """Test coverage retrieval with different repo_id values.""" - # Setup mock_result = Mock() mock_result.data = sample_coverage_data mock_supabase_chain.execute.return_value = mock_result @@ -220,16 +197,13 @@ def test_get_coverages_with_different_repo_id( repo_id = 999 filenames = ["src/main.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert len(result) == 2 mock_supabase_chain.eq.assert_called_once_with("repo_id", repo_id) def test_get_coverages_with_special_file_paths(self, mock_supabase_chain): """Test coverage retrieval with special characters in file paths.""" - # Setup special_data = [ { "id": 1, @@ -251,17 +225,14 @@ def test_get_coverages_with_special_file_paths(self, mock_supabase_chain): "src/file_with_underscores.py", ] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert len(result) == 1 assert "src/file with spaces.py" in result assert result["src/file with spaces.py"]["line_coverage"] == 80.0 def test_get_coverages_with_zero_coverage(self, mock_supabase_chain): """Test coverage retrieval with zero coverage values.""" - # Setup zero_coverage_data = [ { "id": 1, @@ -280,10 +251,8 @@ def test_get_coverages_with_zero_coverage(self, mock_supabase_chain): repo_id = 123 filenames = ["src/uncovered.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert len(result) == 1 assert result["src/uncovered.py"]["line_coverage"] == 0.0 assert result["src/uncovered.py"]["function_coverage"] == 0.0 @@ -291,7 +260,6 @@ def test_get_coverages_with_zero_coverage(self, mock_supabase_chain): def test_get_coverages_with_full_coverage(self, mock_supabase_chain): """Test coverage retrieval with 100% coverage values.""" - # Setup full_coverage_data = [ { "id": 1, @@ -310,10 +278,8 @@ def test_get_coverages_with_full_coverage(self, mock_supabase_chain): repo_id = 123 filenames = ["src/perfect.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert len(result) == 1 assert result["src/perfect.py"]["line_coverage"] == 100.0 assert result["src/perfect.py"]["function_coverage"] == 100.0 @@ -321,30 +287,24 @@ def test_get_coverages_with_full_coverage(self, mock_supabase_chain): def test_get_coverages_database_exception(self, mock_supabase_chain): """Test that database exceptions are handled gracefully due to handle_exceptions decorator.""" - # Setup mock_supabase_chain.execute.side_effect = Exception("Database connection error") repo_id = 123 filenames = ["src/test.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify - should return empty dict due to handle_exceptions decorator assert not result def test_get_coverages_supabase_table_exception(self, mock_supabase): """Test that supabase.table exceptions are handled gracefully.""" - # Setup mock_supabase.table.side_effect = Exception("Table access error") repo_id = 123 filenames = ["src/test.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify - should return empty dict due to handle_exceptions decorator assert not result @pytest.mark.parametrize( @@ -360,22 +320,18 @@ def test_get_coverages_with_various_parameters( self, mock_supabase_chain, repo_id, filenames ): """Test get_coverages with various parameter combinations.""" - # Setup mock_result = Mock() mock_result.data = [] mock_supabase_chain.execute.return_value = mock_result - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert not result mock_supabase_chain.eq.assert_called_once_with("repo_id", repo_id) mock_supabase_chain.in_.assert_called_once_with("full_path", filenames) def test_get_coverages_with_none_values_in_data(self, mock_supabase_chain): """Test coverage retrieval when some fields have None values.""" - # Setup data_with_nones = [ { "id": 1, @@ -399,10 +355,8 @@ def test_get_coverages_with_none_values_in_data(self, mock_supabase_chain): repo_id = 123 filenames = ["src/partial.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify assert len(result) == 1 assert result["src/partial.py"]["line_coverage"] == 50.0 assert result["src/partial.py"]["function_coverage"] is None @@ -413,7 +367,6 @@ def test_get_coverages_return_type_cast( self, mock_supabase_chain, sample_coverage_data ): """Test that the return values are properly cast to Coverages type.""" - # Setup mock_result = Mock() mock_result.data = sample_coverage_data mock_supabase_chain.execute.return_value = mock_result @@ -421,18 +374,15 @@ def test_get_coverages_return_type_cast( repo_id = 123 filenames = ["src/main.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify the result structure (cast function doesn't change runtime behavior) assert isinstance(result, dict) - assert isinstance(result["src/main.py"], dict) # At runtime, it's still a dict + assert isinstance(result["src/main.py"], dict) assert "full_path" in result["src/main.py"] assert "line_coverage" in result["src/main.py"] def test_get_coverages_large_filenames_list(self, mock_supabase_chain): """Test coverage retrieval with a large number of filenames.""" - # Setup large_filenames = [f"src/file_{i}.py" for i in range(100)] mock_result = Mock() mock_result.data = [] @@ -440,39 +390,30 @@ def test_get_coverages_large_filenames_list(self, mock_supabase_chain): repo_id = 123 - # Execute result = get_coverages(repo_id=repo_id, filenames=large_filenames) - # Verify assert not result - # With new character-based batching, this should be called once (small filenames fit in one batch) mock_supabase_chain.in_.assert_called_once_with("full_path", large_filenames) def test_get_coverages_duplicate_filenames( self, mock_supabase_chain, sample_coverage_data ): """Test coverage retrieval with duplicate filenames in the list.""" - # Setup mock_result = Mock() - mock_result.data = [sample_coverage_data[0]] # Only one record + mock_result.data = [sample_coverage_data[0]] mock_supabase_chain.execute.return_value = mock_result repo_id = 123 - filenames = ["src/main.py", "src/main.py", "src/main.py"] # Duplicates + filenames = ["src/main.py", "src/main.py", "src/main.py"] - # Execute result = get_coverages(repo_id=repo_id, filenames=filenames) - # Verify - assert len(result) == 1 # Should only have one entry despite duplicates + assert len(result) == 1 assert "src/main.py" in result mock_supabase_chain.in_.assert_called_once_with("full_path", filenames) def test_get_coverages_character_batching(self, mock_supabase): """Test that batching occurs based on character count.""" - # Setup - Create filenames that will require multiple batches - # Generate filenames that total > 20,000 chars - # 180 files with 110 char paths = 19,800 chars + overhead long_filenames = [ f"src/very/long/path/to/deeply/nested/component/with/many/extra/folders/to/exceed/the/character/limit/easily/file{i:04d}.tsx" for i in range(180) @@ -490,24 +431,19 @@ def test_get_coverages_character_batching(self, mock_supabase): repo_id = 123 - # Execute get_coverages(repo_id=repo_id, filenames=long_filenames) - # Verify - Should be called twice due to character limit assert mock_chain.execute.call_count == 2 assert mock_chain.in_.call_count == 2 - # Check that batches were split correctly first_batch = mock_chain.in_.call_args_list[0][0][1] second_batch = mock_chain.in_.call_args_list[1][0][1] - # All files should be included across batches assert len(first_batch) + len(second_batch) == 180 assert set(first_batch + second_batch) == set(long_filenames) def test_get_coverages_exact_character_limit(self, mock_supabase): """Test that we correctly handle queries near the 25,036 character limit.""" - # Setup mock mock_chain = Mock() mock_supabase.table.return_value = mock_chain mock_chain.select.return_value = mock_chain @@ -517,30 +453,24 @@ def test_get_coverages_exact_character_limit(self, mock_supabase): mock_result.data = [] mock_chain.execute.return_value = mock_result - # Test 1: Just under limit (should be single batch) - # 19,900 chars total (well under 20,000 limit) filenames_under = [ "a" * 195 for _ in range(100) - ] # 100 files × 195 chars = 19,500 + overhead + ] get_coverages(repo_id=123, filenames=filenames_under) assert mock_chain.execute.call_count == 1 - # Reset mock mock_chain.reset_mock() - # Test 2: Over limit (should be multiple batches) - # 22,000+ chars total (over 20,000 limit) filenames_over = [ "b" * 215 for _ in range(100) - ] # 100 files × 215 chars = 21,500 + overhead + ] get_coverages(repo_id=123, filenames=filenames_over) assert mock_chain.execute.call_count == 2 def test_get_coverages_agent_zx_scenario(self, mock_supabase): """Test the exact AGENT-ZX error scenario with many long filenames.""" - # Recreate the exact scenario from AGENT-ZX error filenames = [ "src/createGenericServer.ts", "src/features.ts", @@ -555,7 +485,6 @@ def test_get_coverages_agent_zx_scenario(self, mock_supabase): "src/context/mongodb.ts", ] + [f"src/context/amTrust/file{i:04d}.ts" for i in range(600)] - # Setup mock mock_chain = Mock() mock_supabase.table.return_value = mock_chain mock_chain.select.return_value = mock_chain @@ -565,24 +494,20 @@ def test_get_coverages_agent_zx_scenario(self, mock_supabase): mock_result.data = [] mock_chain.execute.return_value = mock_result - # This should not raise an exception with the fix result = get_coverages(repo_id=297717337, filenames=filenames) - # Should have made 2 batches (total ~20,735 chars) assert mock_chain.execute.call_count == 2 assert isinstance(result, dict) def test_get_coverages_mixed_filename_lengths(self, mock_supabase): """Test batching with mixed short and long filenames.""" - # Mix of very short and very long filenames filenames = ( - ["a.py"] * 100 # 4 chars each - + ["src/medium/path/file.js"] * 100 # 24 chars each + ["a.py"] * 100 + + ["src/medium/path/file.js"] * 100 + ["src/very/long/path/to/deeply/nested/component/file.tsx"] - * 100 # 55 chars each + * 100 ) - # Setup mock mock_chain = Mock() mock_supabase.table.return_value = mock_chain mock_chain.select.return_value = mock_chain @@ -594,14 +519,11 @@ def test_get_coverages_mixed_filename_lengths(self, mock_supabase): result = get_coverages(repo_id=123, filenames=filenames) - # Total: (4+3)*100 + (24+3)*100 + (55+3)*100 + 100 = 9,600 chars - # Should fit in single batch assert mock_chain.execute.call_count == 1 assert isinstance(result, dict) def test_get_coverages_single_very_long_filename_exceeds_limit(self, mock_supabase): """Test edge case where a single filename exceeds the character limit.""" - # Create a filename that exceeds MAX_CHARS (20,000) very_long_filename = "src/" + "x" * 25000 + ".py" mock_chain = Mock() @@ -613,21 +535,15 @@ def test_get_coverages_single_very_long_filename_exceeds_limit(self, mock_supaba mock_result.data = [] mock_chain.execute.return_value = mock_result - # Execute result = get_coverages(repo_id=123, filenames=[very_long_filename]) - # Verify - should still process the single file assert mock_chain.execute.call_count == 1 assert isinstance(result, dict) mock_chain.in_.assert_called_once_with("full_path", [very_long_filename]) def test_get_coverages_empty_batch_condition(self, mock_supabase): """Test the edge case where current_chars + filename_chars > MAX_CHARS but batch is empty.""" - # This tests the condition on line 31: if current_chars + filename_chars > MAX_CHARS and batch: - # We want to test when the condition is true but batch is empty (should not enter the if block) - - # Create a scenario where the first filename itself is very long - long_filename = "src/" + "x" * 19950 + ".py" # Just under limit + long_filename = "src/" + "x" * 19950 + ".py" normal_filename = "src/normal.py" mock_chain = Mock() @@ -639,25 +555,19 @@ def test_get_coverages_empty_batch_condition(self, mock_supabase): mock_result.data = [] mock_chain.execute.return_value = mock_result - # Execute with the long filename first, then normal filename result = get_coverages(repo_id=123, filenames=[long_filename, normal_filename]) - # Should make 2 calls - one for each filename due to character limit assert mock_chain.execute.call_count == 2 assert isinstance(result, dict) def test_get_coverages_batch_reset_after_processing(self, mock_supabase): """Test that batch is properly reset after processing a batch.""" - # Create filenames that will trigger multiple batches - # First batch: files that together exceed the limit - # Each file: 4 + 9950 + 5 = 9959 chars, +3 for quotes/comma = 9962 chars - # First file: 100 + 9962 = 10062, Second file would make: 10062 + 9962 = 20024 > 20000 batch1_files = [ "src/" + "x" * 9950 + f"_{i}.py" for i in range(2) - ] # Will trigger batching + ] batch2_files = [ "src/small.py" - ] # Will be in second batch with second large file + ] all_files = batch1_files + batch2_files @@ -670,30 +580,23 @@ def test_get_coverages_batch_reset_after_processing(self, mock_supabase): mock_result.data = [] mock_chain.execute.return_value = mock_result - # Execute result = get_coverages(repo_id=123, filenames=all_files) - # Should make 2 calls due to batching assert mock_chain.execute.call_count == 2 assert isinstance(result, dict) - # Verify the batches were called correctly first_batch_call = mock_chain.in_.call_args_list[0][0][1] second_batch_call = mock_chain.in_.call_args_list[1][0][1] - # First batch should have only the first large file assert len(first_batch_call) == 1 assert first_batch_call[0] == batch1_files[0] - # Second batch should have the second large file and the small file assert len(second_batch_call) == 2 assert batch1_files[1] in second_batch_call assert "src/small.py" in second_batch_call def test_get_coverages_final_batch_processing(self, mock_supabase): """Test that the final batch is processed correctly when it exists.""" - # Create a scenario where we have files that don't trigger mid-loop batching - # but still need final batch processing - filenames = [f"src/file_{i}.py" for i in range(10)] # Small files, single batch + filenames = [f"src/file_{i}.py" for i in range(10)] mock_chain = Mock() mock_supabase.table.return_value = mock_chain @@ -704,10 +607,8 @@ def test_get_coverages_final_batch_processing(self, mock_supabase): mock_result.data = [] mock_chain.execute.return_value = mock_result - # Execute result = get_coverages(repo_id=123, filenames=filenames) - # Should make 1 call for the final batch assert mock_chain.execute.call_count == 1 assert isinstance(result, dict) mock_chain.in_.assert_called_once_with("full_path", filenames) @@ -716,7 +617,6 @@ def test_get_coverages_multiple_batches_with_data( self, mock_supabase, sample_coverage_data ): """Test that data from multiple batches is correctly combined.""" - # Create filenames that will require multiple batches long_filenames = [ f"src/very/long/path/to/deeply/nested/component/with/many/extra/folders/to/exceed/the/character/limit/easily/file{i:04d}.tsx" for i in range(180) @@ -728,7 +628,6 @@ def test_get_coverages_multiple_batches_with_data( mock_chain.eq.return_value = mock_chain mock_chain.in_.return_value = mock_chain - # Setup different data for each batch batch1_data = [sample_coverage_data[0]] batch2_data = [sample_coverage_data[1]] @@ -737,10 +636,8 @@ def test_get_coverages_multiple_batches_with_data( mock_results[1].data = batch2_data mock_chain.execute.side_effect = mock_results - # Execute result = get_coverages(repo_id=123, filenames=long_filenames) - # Should make 2 calls and combine data from both assert mock_chain.execute.call_count == 2 assert len(result) == 2 assert "src/main.py" in result @@ -748,13 +645,9 @@ def test_get_coverages_multiple_batches_with_data( def test_get_coverages_character_counting_accuracy(self, mock_supabase): """Test that character counting includes quotes and commas correctly.""" - # Each filename needs quotes and comma: "filename", - # So a filename of length N contributes N + 3 characters - - # Create filenames where we can predict the exact character count filenames = [ "a" * 100 for _ in range(190) - ] # 190 files × 103 chars = 19,570 + 100 overhead = 19,670 + ] mock_chain = Mock() mock_supabase.table.return_value = mock_chain @@ -765,18 +658,14 @@ def test_get_coverages_character_counting_accuracy(self, mock_supabase): mock_result.data = [] mock_chain.execute.return_value = mock_result - # Execute result = get_coverages(repo_id=123, filenames=filenames) - # Should fit in single batch (under 20,000 limit) assert mock_chain.execute.call_count == 1 assert isinstance(result, dict) def test_get_coverages_overhead_calculation(self, mock_supabase): """Test that the OVERHEAD constant is properly accounted for in batching.""" - # Test with filenames that would exceed limit only when overhead is considered - # 199 files × 100 chars = 19,900 + 100 overhead = 20,000 (exactly at limit) - filenames = ["x" * 97 for _ in range(199)] # 97 + 3 = 100 chars per file + filenames = ["x" * 97 for _ in range(199)] mock_chain = Mock() mock_supabase.table.return_value = mock_chain @@ -787,13 +676,163 @@ def test_get_coverages_overhead_calculation(self, mock_supabase): mock_result.data = [] mock_chain.execute.return_value = mock_result - # Execute result = get_coverages(repo_id=123, filenames=filenames) - # Should fit in single batch (exactly at limit) assert mock_chain.execute.call_count == 1 assert isinstance(result, dict) + def test_get_coverages_batch_with_none_data_in_middle(self, mock_supabase): + """Test that None data in middle batch doesn't affect final result.""" + long_filenames = [ + f"src/very/long/path/to/deeply/nested/component/with/many/extra/folders/to/exceed/the/character/limit/easily/file{i:04d}.tsx" + for i in range(180) + ] + + mock_chain = Mock() + mock_supabase.table.return_value = mock_chain + mock_chain.select.return_value = mock_chain + mock_chain.eq.return_value = mock_chain + mock_chain.in_.return_value = mock_chain + + mock_results = [Mock(), Mock()] + mock_results[0].data = None + mock_results[1].data = [] + mock_chain.execute.side_effect = mock_results + + result = get_coverages(repo_id=123, filenames=long_filenames) + + assert mock_chain.execute.call_count == 2 + assert isinstance(result, dict) + assert len(result) == 0 + + def test_get_coverages_batch_with_empty_data_in_middle(self, mock_supabase): + """Test that empty data in middle batch doesn't affect final result.""" + long_filenames = [ + f"src/very/long/path/to/deeply/nested/component/with/many/extra/folders/to/exceed/the/character/limit/easily/file{i:04d}.tsx" + for i in range(180) + ] + + mock_chain = Mock() + mock_supabase.table.return_value = mock_chain + mock_chain.select.return_value = mock_chain + mock_chain.eq.return_value = mock_chain + mock_chain.in_.return_value = mock_chain + + mock_results = [Mock(), Mock()] + mock_results[0].data = [] + mock_results[1].data = [] + mock_chain.execute.side_effect = mock_results + + result = get_coverages(repo_id=123, filenames=long_filenames) + + assert mock_chain.execute.call_count == 2 + assert isinstance(result, dict) + assert len(result) == 0 + + def test_get_coverages_exception_in_first_batch(self, mock_supabase): + """Test that exception in first batch is handled gracefully.""" + long_filenames = [ + f"src/very/long/path/to/deeply/nested/component/with/many/extra/folders/to/exceed/the/character/limit/easily/file{i:04d}.tsx" + for i in range(180) + ] + + mock_chain = Mock() + mock_supabase.table.return_value = mock_chain + mock_chain.select.return_value = mock_chain + mock_chain.eq.return_value = mock_chain + mock_chain.in_.return_value = mock_chain + mock_chain.execute.side_effect = Exception("Database error") + + result = get_coverages(repo_id=123, filenames=long_filenames) + + assert isinstance(result, dict) + assert len(result) == 0 + + def test_get_coverages_exception_in_final_batch(self, mock_supabase): + """Test that exception in final batch is handled gracefully.""" + long_filenames = [ + f"src/very/long/path/to/deeply/nested/component/with/many/extra/folders/to/exceed/the/character/limit/easily/file{i:04d}.tsx" + for i in range(180) + ] + + mock_chain = Mock() + mock_supabase.table.return_value = mock_chain + mock_chain.select.return_value = mock_chain + mock_chain.eq.return_value = mock_chain + mock_chain.in_.return_value = mock_chain + + mock_results = [Mock(), Exception("Database error in final batch")] + mock_results[0].data = [] + mock_chain.execute.side_effect = mock_results + + result = get_coverages(repo_id=123, filenames=long_filenames) + + assert isinstance(result, dict) + assert len(result) == 0 + + def test_get_coverages_all_batches_processed_in_loop(self, mock_supabase): + """Test scenario where all files are processed in batches during the loop.""" + filenames = [ + "src/" + "x" * 9950 + f"_{i}.py" for i in range(4) + ] + + mock_chain = Mock() + mock_supabase.table.return_value = mock_chain + mock_chain.select.return_value = mock_chain + mock_chain.eq.return_value = mock_chain + mock_chain.in_.return_value = mock_chain + mock_result = Mock() + mock_result.data = [] + mock_chain.execute.return_value = mock_result + + result = get_coverages(repo_id=123, filenames=filenames) + + assert mock_chain.execute.call_count == 4 + assert isinstance(result, dict) + + def test_get_coverages_final_batch_with_data( + self, mock_supabase_chain, sample_coverage_data + ): + """Test that final batch correctly processes data.""" + mock_result = Mock() + mock_result.data = sample_coverage_data + mock_supabase_chain.execute.return_value = mock_result + + filenames = ["src/main.py", "src/utils.py"] + + result = get_coverages(repo_id=123, filenames=filenames) + + assert len(result) == 2 + assert "src/main.py" in result + assert "src/utils.py" in result + mock_supabase_chain.execute.assert_called_once() + + def test_get_coverages_final_batch_with_none_data(self, mock_supabase_chain): + """Test that final batch handles None data correctly.""" + mock_result = Mock() + mock_result.data = None + mock_supabase_chain.execute.return_value = mock_result + + filenames = ["src/test.py"] + + result = get_coverages(repo_id=123, filenames=filenames) + + assert len(result) == 0 + mock_supabase_chain.execute.assert_called_once() + + def test_get_coverages_final_batch_with_empty_data(self, mock_supabase_chain): + """Test that final batch handles empty data correctly.""" + mock_result = Mock() + mock_result.data = [] + mock_supabase_chain.execute.return_value = mock_result + + filenames = ["src/test.py"] + + result = get_coverages(repo_id=123, filenames=filenames) + + assert len(result) == 0 + mock_supabase_chain.execute.assert_called_once() + class TestGetCoveragesIntegration: """Integration tests that hit the actual Supabase database.""" @@ -801,19 +840,16 @@ class TestGetCoveragesIntegration: @pytest.mark.skipif(bool(os.getenv("CI")), reason="Skip integration tests in CI") def test_find_exact_character_limit(self): """Integration test to find the exact character limit for Supabase queries.""" - # Suppress error logging for this test logging.disable(logging.ERROR) try: - # Binary search for exact character limit low, high = 20000, 30000 max_working = 0 while low <= high: mid = (low + high) // 2 - # Create a single filename with exact length - overhead = 60 # Query structure overhead + overhead = 60 filename_length = mid - overhead filename = "x" * filename_length @@ -823,7 +859,7 @@ def test_find_exact_character_limit(self): ).execute() max_working = mid low = mid + 1 - except Exception as e: # pylint: disable=broad-exception-caught + except Exception as e: if ( "400" in str(e) or "Bad Request" in str(e) @@ -832,10 +868,8 @@ def test_find_exact_character_limit(self): ): high = mid - 1 else: - # Different error, skip high = mid - 1 - # We found the limit to be 25,036 chars assert ( 25000 <= max_working <= 26000 ), f"Expected limit around 25,036, got {max_working}" @@ -846,7 +880,6 @@ def test_find_exact_character_limit(self): @pytest.mark.skipif(bool(os.getenv("CI")), reason="Skip integration tests in CI") def test_get_coverages_with_realistic_large_batch(self): """Test that get_coverages can handle realistic large batches from real repos.""" - # Create filenames similar to the AGENT-ZX error case filenames = [ "src/createGenericServer.ts", "src/features.ts", @@ -861,8 +894,7 @@ def test_get_coverages_with_realistic_large_batch(self): "src/context/mongodb.ts", ] + [ f"src/context/amTrust/file{i}.ts" for i in range(100) - ] # Simulate many amTrust files + ] - # This should not raise an exception result = get_coverages(repo_id=999999, filenames=filenames) assert isinstance(result, dict)