Skip to main content
Glama
barvhaim

Israeli Land Authority MCP Server

by barvhaim
test_performance_e2e.py13.5 kB
""" Performance and load tests for the Israeli Land Authority API """ import pytest import time import statistics from concurrent.futures import ThreadPoolExecutor, as_completed from typing import List, Dict, Any from src.remy_mcp.client import IsraeliLandAPI from tests.utils import ( PerformanceTracker, timed_api_call, APITestHelper, requires_api_access, skip_if_slow, ) class TestPerformanceE2E: """Performance tests for API client""" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_api_response_times(self): """Test API response times are within acceptable limits""" api_client = IsraeliLandAPI(rate_limit_delay=1.1) # Slightly above 1 second tracker = PerformanceTracker() # Test multiple calls for i in range(5): with timed_api_call(tracker): results = api_client.search_tenders(page_size=5) assert results is not None stats = tracker.get_stats() # Response times should be reasonable assert ( stats["avg_response_time"] < 10.0 ), f"Average response time too high: {stats['avg_response_time']}" assert ( stats["max_response_time"] < 30.0 ), f"Max response time too high: {stats['max_response_time']}" assert stats["error_rate"] == 0.0, f"Unexpected errors: {stats['error_rate']}" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_rate_limiting_effectiveness(self): """Test that rate limiting properly enforces delays""" api_client = IsraeliLandAPI(rate_limit_delay=2.0) start_time = time.time() # Make 3 consecutive calls for i in range(3): api_client.search_tenders(page_size=1) end_time = time.time() total_time = end_time - start_time # Should take at least 4 seconds (2 delays between 3 calls) assert total_time >= 4.0, f"Rate limiting not working: {total_time} seconds" # But not too much longer (allowing for API response time) assert total_time < 10.0, f"Rate limiting too aggressive: {total_time} seconds" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_concurrent_request_handling(self): """Test handling of concurrent requests with rate limiting""" def make_api_call(client_id: int) -> Dict[str, Any]: """Make an API call and return timing info""" api_client = IsraeliLandAPI(rate_limit_delay=1.0) start_time = time.time() try: results = api_client.search_tenders(page_size=2) end_time = time.time() return { "client_id": client_id, "success": True, "duration": end_time - start_time, "result_count": ( len(results) if isinstance(results, list) else len(results.get("results", [])) ), } except Exception as e: end_time = time.time() return { "client_id": client_id, "success": False, "duration": end_time - start_time, "error": str(e), } # Test with 3 concurrent requests with ThreadPoolExecutor(max_workers=3) as executor: futures = [executor.submit(make_api_call, i) for i in range(3)] results = [future.result() for future in as_completed(futures)] # All requests should succeed successful_results = [r for r in results if r["success"]] assert len(successful_results) == 3, f"Some requests failed: {results}" # Check timing - each should be properly rate limited durations = [r["duration"] for r in successful_results] avg_duration = statistics.mean(durations) # Average should be reasonable (rate limiting + API response time) assert avg_duration >= 1.0, "Rate limiting not applied to concurrent requests" assert avg_duration < 15.0, f"Concurrent requests too slow: {avg_duration}" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_large_result_set_performance(self): """Test performance with larger result sets""" api_client = IsraeliLandAPI(rate_limit_delay=0.5) tracker = PerformanceTracker() # Test with different page sizes page_sizes = [10, 50, 100] for page_size in page_sizes: with timed_api_call(tracker): results = api_client.search_tenders(page_size=page_size) assert results is not None # Verify we got results up to the requested size result_list = ( results if isinstance(results, list) else results.get("results", []) ) assert len(result_list) <= page_size stats = tracker.get_stats() # Larger results shouldn't be dramatically slower assert ( stats["max_response_time"] < 20.0 ), f"Large result sets too slow: {stats['max_response_time']}" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_details_endpoint_performance(self): """Test performance of tender details endpoint""" helper = APITestHelper(rate_limit_delay=1.1) # Get a sample tender ID tender_id = helper.get_sample_tender_id() if not tender_id: pytest.skip("No tender ID available for testing") tracker = PerformanceTracker() # Test details endpoint multiple times for i in range(3): with timed_api_call(tracker): details = helper.api_client.get_tender_details(tender_id) assert details is not None assert "MichrazID" in details stats = tracker.get_stats() # Details endpoint should be reasonably fast assert ( stats["avg_response_time"] < 8.0 ), f"Details endpoint too slow: {stats['avg_response_time']}" assert stats["error_rate"] == 0.0, "Details endpoint errors" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_map_endpoint_performance(self): """Test performance of map details endpoint""" helper = APITestHelper(rate_limit_delay=1.1) # Get a sample tender ID tender_id = helper.get_sample_tender_id() if not tender_id: pytest.skip("No tender ID available for testing") tracker = PerformanceTracker() # Test map endpoint for i in range(2): # Fewer iterations as this might be slower with timed_api_call(tracker): map_details = helper.api_client.get_tender_map_details(tender_id) # Map details might be empty, but should not error assert map_details is not None stats = tracker.get_stats() # Map endpoint might be slower, but should still be reasonable assert ( stats["avg_response_time"] < 15.0 ), f"Map endpoint too slow: {stats['avg_response_time']}" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_search_with_complex_filters_performance(self): """Test performance with complex search filters""" api_client = IsraeliLandAPI(rate_limit_delay=1.1) tracker = PerformanceTracker() # Complex search parameters complex_searches = [ { "tender_types": [1, 2, 3], "regions": [4, 5], "active_only": True, "page_size": 20, }, { "tender_types": [1], "priority_populations": [1, 3, 6], "quick_search": True, "page_size": 15, }, {"regions": [4], "tender_statuses": [1], "page_size": 10}, ] for search_params in complex_searches: with timed_api_call(tracker): results = api_client.search_tenders(**search_params) assert results is not None stats = tracker.get_stats() # Complex searches might be slower but should still be acceptable assert ( stats["avg_response_time"] < 12.0 ), f"Complex searches too slow: {stats['avg_response_time']}" assert stats["error_rate"] == 0.0, "Complex search errors" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_pagination_performance(self): """Test performance of pagination""" api_client = IsraeliLandAPI(rate_limit_delay=1.1) tracker = PerformanceTracker() # Test multiple pages for page_num in range(1, 4): # Test first 3 pages with timed_api_call(tracker): results = api_client.search_tenders(page_size=10, page_number=page_num) assert results is not None stats = tracker.get_stats() # Pagination performance should be consistent response_times = tracker.call_times if len(response_times) >= 3: # Check that later pages aren't significantly slower time_variance = statistics.stdev(response_times) avg_time = statistics.mean(response_times) # Variance should be reasonable (not more than 50% of average) assert ( time_variance < avg_time * 0.5 ), f"High pagination variance: {time_variance}" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_error_recovery_performance(self): """Test performance of error recovery mechanisms""" api_client = IsraeliLandAPI(rate_limit_delay=0.5) # Test with invalid parameters that should cause errors start_time = time.time() try: # This should fail quickly api_client.get_tender_details(-1) except Exception: pass # Expected error_time = time.time() - start_time # Error handling should be fast (not timing out) assert error_time < 5.0, f"Error handling too slow: {error_time}" # After error, normal requests should still work start_time = time.time() results = api_client.search_tenders(page_size=1) recovery_time = time.time() - start_time assert results is not None assert recovery_time < 10.0, f"Recovery after error too slow: {recovery_time}" @pytest.mark.e2e @pytest.mark.slow @requires_api_access def test_memory_usage_with_large_results(self): """Test memory usage doesn't grow excessively with large results""" import psutil import os api_client = IsraeliLandAPI(rate_limit_delay=1.1) # Get initial memory usage process = psutil.Process(os.getpid()) initial_memory = process.memory_info().rss / 1024 / 1024 # MB # Make several requests for larger datasets for i in range(3): results = api_client.search_tenders(page_size=100) assert results is not None # Clear reference to results del results # Check memory usage after final_memory = process.memory_info().rss / 1024 / 1024 # MB memory_increase = final_memory - initial_memory # Memory increase should be reasonable (less than 100MB) assert ( memory_increase < 100 ), f"Excessive memory usage: {memory_increase}MB increase" @pytest.mark.e2e @pytest.mark.slow @skip_if_slow def test_sustained_load_performance(self): """Test performance under sustained load""" api_client = IsraeliLandAPI(rate_limit_delay=1.0) tracker = PerformanceTracker() # Run sustained requests for 2 minutes start_time = time.time() request_count = 0 while time.time() - start_time < 120: # 2 minutes with timed_api_call(tracker): try: results = api_client.search_tenders(page_size=5) assert results is not None request_count += 1 except Exception as e: # Allow some errors under sustained load tracker.record_call(0, 0, True) stats = tracker.get_stats() # Should have made reasonable number of requests assert request_count >= 90, f"Too few requests completed: {request_count}" # Error rate should be low assert ( stats["error_rate"] <= 0.1 ), f"High error rate under load: {stats['error_rate']}" # Performance should remain stable response_times = tracker.call_times if len(response_times) >= 10: first_half = response_times[: len(response_times) // 2] second_half = response_times[len(response_times) // 2 :] avg_first = statistics.mean(first_half) avg_second = statistics.mean(second_half) # Performance shouldn't degrade significantly over time degradation = (avg_second - avg_first) / avg_first if avg_first > 0 else 0 assert ( degradation < 0.5 ), f"Performance degraded under load: {degradation * 100}%"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/barvhaim/remy-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server