Skip to main content
Glama

Dynamic Per-User Tool Generation MCP Server

test_elasticsearch_entities.py29.8 kB
""" Test Elasticsearch Entity Search Functionality Tests searching for all 11 entity types with real Elasticsearch connection. Assumes Elasticsearch service is running on localhost:9200. """ import pytest import asyncio import sys import os from pathlib import Path # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent)) from elasticsearch_search_lib import SearchClient from elasticsearch_search_lib.exceptions import EntityNotFoundError, ValidationError # Test configuration TENANT_ID = os.getenv("TENANT_ID", "apolo") ES_HOST = os.getenv("ES_HOST", "localhost") ES_PORT = int(os.getenv("ES_PORT", "9200")) @pytest.fixture def search_client(): """Create a search client for testing.""" return SearchClient( tenant_id=TENANT_ID, es_host=ES_HOST, es_port=ES_PORT ) class TestElasticsearchConnection: """Test Elasticsearch connection and basic health.""" @pytest.mark.asyncio async def test_elasticsearch_is_running(self, search_client): """Test that Elasticsearch service is accessible and healthy.""" try: # Verify entity types are loaded entities = search_client.get_supported_entities() assert len(entities) == 11, f"Expected 11 entity types, got {len(entities)}" # Verify all expected entity types are present expected_entities = [ 'impact', 'urgency', 'priority', 'status', 'category', 'source', 'location', 'department', 'usergroup', 'user', 'vendor' ] for entity in expected_entities: assert entity in entities, f"Missing entity type: {entity}" print(f"✓ Elasticsearch connection verified") print(f"✓ All {len(entities)} entity types available") except Exception as e: pytest.fail(f"Elasticsearch connection failed: {e}") @pytest.mark.asyncio async def test_entity_configuration_loaded(self, search_client): """Test that entity configurations are properly loaded.""" try: # Test configuration for each entity type for entity_type in ['user', 'status', 'location']: config = search_client.get_entity_config(entity_type) # Validate configuration structure assert config.entity_type == entity_type, f"Entity type mismatch for {entity_type}" assert config.default_limit > 0, f"Default limit should be positive for {entity_type}" assert config.max_limit >= config.default_limit, f"Max limit should be >= default limit for {entity_type}" assert config.min_score >= 0, f"Min score should be non-negative for {entity_type}" assert len(config.fields) > 0, f"Entity {entity_type} should have fields" # Validate field configurations for field in config.fields: assert field.name, f"Field should have a name in {entity_type}" assert field.boost > 0, f"Field boost should be positive in {entity_type}" print(f"✓ Entity configurations properly loaded and validated") except Exception as e: pytest.fail(f"Entity configuration test failed: {e}") class TestSimpleEntities: """Test simple entity types (Impact, Urgency, Priority).""" @pytest.mark.asyncio async def test_search_impact(self, search_client): """Test searching for Impact entities.""" try: result = await search_client.search("impact", "Productivity Loss", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "impact" assert result.index_name == f"{TENANT_ID}_impact" print(f"✓ Impact search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] assert 'impact_name' in first_item.data or 'impact_id' in first_item.data print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"Impact search failed: {e}") @pytest.mark.asyncio async def test_search_urgency(self, search_client): """Test searching for Urgency entities.""" try: result = await search_client.search("urgency", "medium", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "urgency" assert result.index_name == f"{TENANT_ID}_urgency" print(f"✓ Urgency search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] assert 'urgency_name' in first_item.data or 'urgency_id' in first_item.data print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"Urgency search failed: {e}") @pytest.mark.asyncio async def test_search_priority(self, search_client): """Test searching for Priority entities.""" try: result = await search_client.search("priority", "low", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "priority" assert result.index_name == f"{TENANT_ID}_priority" print(f"✓ Priority search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] assert 'priority_name' in first_item.data or 'priority_id' in first_item.data print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"Priority search failed: {e}") class TestModelBasedEntities: """Test model-based entity types (Status, Category, Source).""" @pytest.mark.asyncio async def test_search_status(self, search_client): """Test searching for Status entities.""" try: result = await search_client.search("status", "open", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "status" assert result.index_name == f"{TENANT_ID}_status" print(f"✓ Status search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] assert 'status_name' in first_item.data or 'status_id' in first_item.data print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"Status search failed: {e}") @pytest.mark.asyncio async def test_search_category(self, search_client): """Test searching for Category entities.""" try: result = await search_client.search("category", "hardware", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "category" assert result.index_name == f"{TENANT_ID}_category" print(f"✓ Category search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] assert 'category_name' in first_item.data or 'category_id' in first_item.data print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"Category search failed: {e}") @pytest.mark.asyncio async def test_search_source(self, search_client): """Test searching for Source entities.""" try: result = await search_client.search("source", "email", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "source" assert result.index_name == f"{TENANT_ID}_source" print(f"✓ Source search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] assert 'source_name' in first_item.data or 'source_id' in first_item.data print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"Source search failed: {e}") class TestHierarchicalEntities: """Test hierarchical entity types (Location, Department).""" @pytest.mark.asyncio async def test_search_location(self, search_client): """Test searching for Location entities.""" try: result = await search_client.search("location", "office", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "location" assert result.index_name == f"{TENANT_ID}_location" print(f"✓ Location search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] # Location has hierarchy, id, parentId, name fields assert any(key.startswith('location_') for key in first_item.data.keys()) print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"Location search failed: {e}") @pytest.mark.asyncio async def test_search_department(self, search_client): """Test searching for Department entities.""" try: result = await search_client.search("department", "it", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "department" assert result.index_name == f"{TENANT_ID}_department" print(f"✓ Department search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] # Department has hierarchy, id, parentId, name fields assert any(key.startswith('department_') for key in first_item.data.keys()) print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"Department search failed: {e}") class TestComplexEntities: """Test complex entity types (UserGroup, User, Vendor).""" @pytest.mark.asyncio async def test_search_usergroup(self, search_client): """Test searching for UserGroup entities.""" try: result = await search_client.search("usergroup", "admin", limit=5) # UserGroup index may not exist in all environments if result.success: assert result.entity_type == "usergroup" assert result.index_name == f"{TENANT_ID}_usergroup" print(f"✓ UserGroup search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] assert any(key.startswith('usergroup_') for key in first_item.data.keys()) print(f" Sample: {first_item.data}") else: # Index may not exist - that's okay print(f"⚠ UserGroup search: {result.error} (index may not exist)") except Exception as e: pytest.fail(f"UserGroup search failed: {e}") @pytest.mark.asyncio async def test_search_user(self, search_client): """Test searching for User entities.""" try: result = await search_client.search("user", "test", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "user" assert result.index_name == f"{TENANT_ID}_user" print(f"✓ User search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] # User has multiple fields: name, email, contact, userlogonname, contact2, type assert any(key.startswith('user_') for key in first_item.data.keys()) print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"User search failed: {e}") @pytest.mark.asyncio async def test_search_vendor(self, search_client): """Test searching for Vendor entities.""" try: result = await search_client.search("vendor", "tech", limit=5) assert result.success, f"Search failed: {result.error}" assert result.entity_type == "vendor" assert result.index_name == f"{TENANT_ID}_vendor" print(f"✓ Vendor search: {result.total_hits} hits, {result.returned_count} returned") if result.items: first_item = result.items[0] # Vendor has: name, id, email, contact, description assert any(key.startswith('vendor_') for key in first_item.data.keys()) print(f" Sample: {first_item.data}") except Exception as e: pytest.fail(f"Vendor search failed: {e}") class TestSearchValidation: """Test search validation and error handling.""" @pytest.mark.asyncio async def test_invalid_entity_type(self, search_client): """Test searching with invalid entity type.""" try: # The search handler catches exceptions and returns error response result = await search_client.search("invalid_entity", "test") assert not result.success, "Search should fail for invalid entity" assert "invalid_entity" in result.error.lower() or "not found" in result.error.lower() print(f"✓ Invalid entity type returns error: {result.error}") except Exception as e: pytest.fail(f"Invalid entity type test failed: {e}") @pytest.mark.asyncio async def test_empty_query(self, search_client): """Test searching with empty query.""" try: # The search handler catches exceptions and returns error response result = await search_client.search("user", "") assert not result.success, "Search should fail for empty query" assert "empty" in result.error.lower() or "cannot be empty" in result.error.lower() print(f"✓ Empty query returns error: {result.error}") except Exception as e: pytest.fail(f"Empty query test failed: {e}") @pytest.mark.asyncio async def test_limit_enforcement(self, search_client): """Test that limit is enforced correctly.""" try: # Get entity config to check max_limit config = search_client.get_entity_config("user") max_limit = config.max_limit # Request more than max_limit result = await search_client.search("user", "test", limit=max_limit + 100) # Should be capped at max_limit assert result.returned_count <= max_limit print(f"✓ Limit enforcement works: requested {max_limit + 100}, got {result.returned_count}") except Exception as e: pytest.fail(f"Limit enforcement test failed: {e}") @pytest.mark.asyncio async def test_pagination(self, search_client): """Test pagination with from_offset.""" try: # Get first page result1 = await search_client.search("user", "test", limit=2, from_offset=0) # Get second page result2 = await search_client.search("user", "test", limit=2, from_offset=2) # Results should be different (if enough data exists) if result1.items and result2.items and result1.total_hits > 2: assert result1.items[0].id != result2.items[0].id print(f"✓ Pagination works: page 1 has {len(result1.items)} items, page 2 has {len(result2.items)} items") else: print("⚠ Not enough data to test pagination fully") except Exception as e: pytest.fail(f"Pagination test failed: {e}") class TestSearchFeatures: """Test search features like fuzzy matching and scoring.""" @pytest.mark.asyncio async def test_fuzzy_search(self, search_client): """Test fuzzy search functionality.""" try: # Search with potential typo result = await search_client.search("user", "tset", limit=5) # Should still return results due to fuzzy matching assert result.success print(f"✓ Fuzzy search works: '{result.query}' returned {result.total_hits} hits") except Exception as e: pytest.fail(f"Fuzzy search test failed: {e}") @pytest.mark.asyncio async def test_score_ordering(self, search_client): """Test that results are ordered by score.""" try: result = await search_client.search("user", "test", limit=10) if len(result.items) > 1: # Check that scores are in descending order scores = [item.score for item in result.items] assert scores == sorted(scores, reverse=True), "Results not ordered by score" print(f"✓ Score ordering works: scores range from {scores[0]:.2f} to {scores[-1]:.2f}") else: print("⚠ Not enough results to test score ordering") except Exception as e: pytest.fail(f"Score ordering test failed: {e}") @pytest.mark.asyncio async def test_min_score_filtering(self, search_client): """Test that min_score filtering works.""" try: result = await search_client.search("user", "test", limit=10) # Get entity config to check min_score config = search_client.get_entity_config("user") min_score = config.min_score # All returned items should have score >= min_score if result.items: for item in result.items: assert item.score >= min_score, f"Item score {item.score} below min_score {min_score}" print(f"✓ Min score filtering works: all scores >= {min_score}") except Exception as e: pytest.fail(f"Min score filtering test failed: {e}") class TestAllEntitiesComprehensive: """Comprehensive test that searches all 11 entity types.""" @pytest.mark.asyncio async def test_all_entities_searchable(self, search_client): """Test that all 11 entity types can be searched.""" all_entities = [ 'impact', 'urgency', 'priority', 'status', 'category', 'source', 'location', 'department', 'usergroup', 'user', 'vendor' ] results = {} for entity_type in all_entities: try: result = await search_client.search(entity_type, "test", limit=1) results[entity_type] = { 'success': result.success, 'total_hits': result.total_hits, 'error': result.error } except Exception as e: results[entity_type] = { 'success': False, 'total_hits': 0, 'error': str(e) } # Print summary print("\n" + "="*60) print("All Entities Search Summary") print("="*60) successful = 0 for entity_type, result in results.items(): status = "✓" if result['success'] else "✗" print(f"{status} {entity_type:12s}: {result['total_hits']:4d} hits") if result['success']: successful += 1 print("="*60) print(f"Total: {successful}/{len(all_entities)} entity types searchable") print("="*60) # Most should be successful (some indices may not exist) # We expect at least 9 out of 11 to be searchable assert successful >= 9, f"Only {successful}/{len(all_entities)} entities searchable (expected at least 9)" class TestRealWorldScenarios: """Test real-world usage scenarios.""" @pytest.mark.asyncio async def test_user_search_by_email(self, search_client): """Scenario: IT admin searches for user by email to assign ticket.""" try: # Search for user by email pattern result = await search_client.search("user", "test", limit=10) # Validate response structure assert result.success or result.total_hits >= 0, "Search should complete" assert result.entity_type == "user", "Should search user entity" # If results found, validate data structure if result.items: for item in result.items: assert 'dbid' in item.data or 'user_name' in item.data, "User should have identifiable data" assert item.score > 0, "Results should have relevance score" print(f"✓ User search scenario: Found {result.total_hits} users, returned {result.returned_count}") else: print(f"✓ User search scenario: No users found (valid result)") except Exception as e: pytest.fail(f"User search scenario failed: {e}") @pytest.mark.asyncio async def test_status_search_for_ticket_update(self, search_client): """Scenario: User wants to update ticket status.""" try: # Search for available statuses result = await search_client.search("status", "open", limit=10) assert result.entity_type == "status", "Should search status entity" # If results found, validate they have required fields if result.items: for item in result.items: # Status should have name and id assert 'status_name' in item.data or 'dbid' in item.data, "Status should have name or id" print(f"✓ Status search scenario: Found {result.total_hits} statuses") else: print(f"✓ Status search scenario: No statuses found") except Exception as e: pytest.fail(f"Status search scenario failed: {e}") @pytest.mark.asyncio async def test_location_hierarchy_search(self, search_client): """Scenario: User searches for location in organizational hierarchy.""" try: # Search for location result = await search_client.search("location", "office", limit=10) assert result.entity_type == "location", "Should search location entity" # If results found, validate hierarchical data if result.items: for item in result.items: # Location should have hierarchical fields data_keys = item.data.keys() has_location_data = any(key.startswith('location_') for key in data_keys) assert has_location_data, "Location should have location-specific fields" print(f"✓ Location hierarchy scenario: Found {result.total_hits} locations") else: print(f"✓ Location hierarchy scenario: No locations found") except Exception as e: pytest.fail(f"Location hierarchy scenario failed: {e}") @pytest.mark.asyncio async def test_category_search_for_incident(self, search_client): """Scenario: User categorizes an incident.""" try: # Search for categories result = await search_client.search("category", "hardware", limit=10) assert result.entity_type == "category", "Should search category entity" # Validate results are ordered by relevance if len(result.items) > 1: scores = [item.score for item in result.items] assert scores == sorted(scores, reverse=True), "Results should be ordered by score" print(f"✓ Category search scenario: Found {result.total_hits} categories, properly ordered") else: print(f"✓ Category search scenario: Found {result.total_hits} categories") except Exception as e: pytest.fail(f"Category search scenario failed: {e}") @pytest.mark.asyncio async def test_fuzzy_search_typo_tolerance(self, search_client): """Scenario: User makes typo while searching.""" try: # Search with intentional typo result_typo = await search_client.search("user", "tset", limit=5) # Fuzzy search should still find results assert result_typo.success or result_typo.total_hits >= 0, "Fuzzy search should complete" if result_typo.total_hits > 0: print(f"✓ Fuzzy search scenario: Typo 'tset' found {result_typo.total_hits} results") else: print(f"✓ Fuzzy search scenario: Typo handled gracefully") except Exception as e: pytest.fail(f"Fuzzy search scenario failed: {e}") @pytest.mark.asyncio async def test_pagination_for_large_results(self, search_client): """Scenario: User browses through multiple pages of results.""" try: # Get first page page1 = await search_client.search("user", "test", limit=3, from_offset=0) # Get second page page2 = await search_client.search("user", "test", limit=3, from_offset=3) # Validate pagination assert page1.entity_type == page2.entity_type, "Both pages should be same entity" assert page1.query == page2.query, "Both pages should have same query" # If enough results, pages should be different if page1.total_hits > 3 and page1.items and page2.items: page1_ids = [item.id for item in page1.items] page2_ids = [item.id for item in page2.items] assert page1_ids != page2_ids, "Different pages should have different results" print(f"✓ Pagination scenario: Page 1 has {len(page1.items)} items, Page 2 has {len(page2.items)} items") else: print(f"✓ Pagination scenario: Not enough data to test pagination fully") except Exception as e: pytest.fail(f"Pagination scenario failed: {e}") @pytest.mark.asyncio async def test_limit_enforcement_scenario(self, search_client): """Scenario: System enforces result limits to prevent overload.""" try: # Get entity config config = search_client.get_entity_config("user") max_limit = config.max_limit # Try to request more than max_limit result = await search_client.search("user", "test", limit=max_limit + 1000) # Should be capped at max_limit assert result.returned_count <= max_limit, f"Results should be capped at {max_limit}" print(f"✓ Limit enforcement scenario: Requested {max_limit + 1000}, got {result.returned_count} (max: {max_limit})") except Exception as e: pytest.fail(f"Limit enforcement scenario failed: {e}") @pytest.mark.asyncio async def test_empty_query_validation(self, search_client): """Scenario: User submits empty search query.""" try: # Try to search with empty query result = await search_client.search("user", "") # Should return error, not crash assert not result.success, "Empty query should fail" assert result.error, "Should have error message" assert "empty" in result.error.lower(), "Error should mention empty query" print(f"✓ Empty query scenario: Properly rejected with message: {result.error}") except Exception as e: pytest.fail(f"Empty query scenario failed: {e}") @pytest.mark.asyncio async def test_cross_entity_search_consistency(self, search_client): """Scenario: Verify search behavior is consistent across entity types.""" try: entity_types = ['user', 'status', 'category', 'location'] for entity_type in entity_types: result = await search_client.search(entity_type, "test", limit=5) # All should have consistent response structure assert hasattr(result, 'success'), f"{entity_type} should have success field" assert hasattr(result, 'entity_type'), f"{entity_type} should have entity_type field" assert hasattr(result, 'total_hits'), f"{entity_type} should have total_hits field" assert hasattr(result, 'items'), f"{entity_type} should have items field" assert result.entity_type == entity_type, f"Entity type should match for {entity_type}" print(f"✓ Cross-entity consistency: All {len(entity_types)} entity types have consistent response structure") except Exception as e: pytest.fail(f"Cross-entity consistency scenario failed: {e}") if __name__ == "__main__": # Run tests with pytest pytest.main([__file__, "-v", "--tb=short", "-s"])

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/ShivamPansuriya/MCP-server-Python'

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