Skip to main content
Glama
test_data_manager.py16.2 kB
""" Property-Based Tests for Data Manager Tests correctness properties using Hypothesis for property-based testing. """ import json import os import tempfile import shutil from hypothesis import given, strategies as st, settings, HealthCheck import pytest from data_manager import ( load_best_practices, get_fallback_guidance, FileLoadError, ValidationError ) # Test fixtures and helpers @pytest.fixture def temp_best_practices_file(): """Create a temporary best practices file for testing.""" temp_dir = tempfile.mkdtemp() temp_file = os.path.join(temp_dir, "test_best_practices.json") # Create a valid test file test_data = { "python_best_practices": { "test_category": { "test_topic": { "description": "Test description", "examples": { "example1": "test code" } } } } } with open(temp_file, 'w') as f: json.dump(test_data, f) # Set environment variable old_env = os.environ.get("BEST_PRACTICES_FILE") os.environ["BEST_PRACTICES_FILE"] = temp_file yield temp_file # Cleanup if old_env: os.environ["BEST_PRACTICES_FILE"] = old_env else: os.environ.pop("BEST_PRACTICES_FILE", None) shutil.rmtree(temp_dir) # Property 11: Dynamic database reloading # Feature: bestpractices-mcp-server, Property 11: Dynamic database reloading def test_dynamic_reload_property(temp_best_practices_file): """ Property 11: For any database modification, the next request should reflect the updated content. Validates: Requirements 3.4, 9.2 """ # Load initial data initial_data = load_best_practices() initial_categories = list(initial_data["python_best_practices"].keys()) # Modify the file modified_data = { "python_best_practices": { "new_category": { "new_topic": { "description": "New description", "examples": { "new_example": "new code" } } } } } with open(temp_best_practices_file, 'w') as f: json.dump(modified_data, f) # Load again - should reflect changes updated_data = load_best_practices() updated_categories = list(updated_data["python_best_practices"].keys()) # Verify the data changed assert initial_categories != updated_categories assert "new_category" in updated_categories assert "new_category" not in initial_categories assert updated_data["python_best_practices"]["new_category"]["new_topic"]["description"] == "New description" @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture]) @given( category_name=st.text(min_size=1, max_size=50, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')) | st.just('_')), topic_name=st.text(min_size=1, max_size=50, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')) | st.just('_')), description=st.text(min_size=1, max_size=200) ) def test_dynamic_reload_with_random_data(temp_best_practices_file, category_name, topic_name, description): """ Property 11 (extended): Dynamic reload works with various data structures. """ # Create data with random content test_data = { "python_best_practices": { category_name: { topic_name: { "description": description, "examples": { "example1": "test" } } } } } with open(temp_best_practices_file, 'w') as f: json.dump(test_data, f) # Load and verify loaded_data = load_best_practices() assert category_name in loaded_data["python_best_practices"] assert topic_name in loaded_data["python_best_practices"][category_name] assert loaded_data["python_best_practices"][category_name][topic_name]["description"] == description # Additional tests for file loading def test_load_missing_file(): """Test that missing file raises appropriate error.""" old_env = os.environ.get("BEST_PRACTICES_FILE") os.environ["BEST_PRACTICES_FILE"] = "/nonexistent/file.json" try: with pytest.raises(FileLoadError) as exc_info: load_best_practices() assert "not found" in str(exc_info.value).lower() finally: if old_env: os.environ["BEST_PRACTICES_FILE"] = old_env else: os.environ.pop("BEST_PRACTICES_FILE", None) def test_load_invalid_json(temp_best_practices_file): """Test that invalid JSON raises appropriate error.""" # Write invalid JSON with open(temp_best_practices_file, 'w') as f: f.write("{ invalid json }") with pytest.raises(ValidationError) as exc_info: load_best_practices() assert "parse" in str(exc_info.value).lower() or "json" in str(exc_info.value).lower() def test_load_missing_root_key(temp_best_practices_file): """Test that missing root key raises validation error.""" with open(temp_best_practices_file, 'w') as f: json.dump({"wrong_key": {}}, f) with pytest.raises(ValidationError) as exc_info: load_best_practices() assert "python_best_practices" in str(exc_info.value) def test_fallback_guidance(): """Test that fallback guidance is provided.""" guidance = get_fallback_guidance() assert "error" in guidance assert "fallback_guidance" in guidance assert "python_basics" in guidance["fallback_guidance"] assert "fastapi_basics" in guidance["fallback_guidance"] assert len(guidance["fallback_guidance"]["python_basics"]) > 0 assert len(guidance["fallback_guidance"]["fastapi_basics"]) > 0 # Property 28: JSON validation before processing # Feature: bestpractices-mcp-server, Property 28: JSON validation before processing def test_json_validation_before_processing_invalid_structure(temp_best_practices_file): """ Property 28: For any database read, validation should happen before processing. Invalid JSON should return validation errors, not processing errors. Validates: Requirements 9.5 """ # Test with invalid structure - should get ValidationError, not processing error invalid_structures = [ {}, # Missing root key {"python_best_practices": "not a dict"}, # Wrong type {"python_best_practices": {}}, # Empty categories {"python_best_practices": {"cat": "not a dict"}}, # Category not dict {"python_best_practices": {"cat": {"topic": {}}}}, # Missing description {"python_best_practices": {"cat": {"topic": {"description": "test"}}}}, # Missing examples ] for invalid_data in invalid_structures: with open(temp_best_practices_file, 'w') as f: json.dump(invalid_data, f) with pytest.raises(ValidationError): load_best_practices() @settings(max_examples=100, suppress_health_check=[HealthCheck.function_scoped_fixture]) @given( has_root=st.booleans(), has_categories=st.booleans(), has_description=st.booleans(), has_examples=st.booleans() ) def test_json_validation_property(temp_best_practices_file, has_root, has_categories, has_description, has_examples): """ Property 28 (extended): Validation catches various structural issues. """ # Build data based on flags if not has_root: data = {"wrong_key": {}} elif not has_categories: data = {"python_best_practices": {}} else: topic_data = {} if has_description: topic_data["description"] = "test" if has_examples: topic_data["examples"] = {"ex1": "code"} data = { "python_best_practices": { "test_cat": { "test_topic": topic_data } } } with open(temp_best_practices_file, 'w') as f: json.dump(data, f) # Should succeed only if all required fields present if has_root and has_categories and has_description and has_examples: result = load_best_practices() assert "python_best_practices" in result else: with pytest.raises((ValidationError, FileLoadError)): load_best_practices() def test_validation_with_tools_instead_of_examples(temp_best_practices_file): """Test that 'tools' field is accepted as alternative to 'examples'.""" data = { "python_best_practices": { "test_category": { "test_topic": { "description": "Test with tools", "tools": { "tool1": "tool code" } } } } } with open(temp_best_practices_file, 'w') as f: json.dump(data, f) # Should load successfully result = load_best_practices() assert "test_category" in result["python_best_practices"] # Property 1: Search completeness # Feature: bestpractices-mcp-server, Property 1: Search completeness @settings(max_examples=100) @given(keyword=st.text(min_size=1, max_size=50)) def test_search_completeness_property(keyword): """ Property 1: For any keyword, all returned search results should contain the keyword. Validates: Requirements 1.1 """ from data_manager import search_practices, load_best_practices try: data = load_best_practices() result = search_practices(keyword, data=data) if "matches" in result and result["count"] > 0: keyword_lower = keyword.lower().strip() for match in result["matches"]: # Convert match to searchable text match_text = json.dumps(match).lower() assert keyword_lower in match_text, f"Keyword '{keyword}' not found in match: {match['topic']}" except Exception: # If loading fails, that's okay for this test pass # Property 2: Search result structure # Feature: bestpractices-mcp-server, Property 2: Search result structure def test_search_result_structure_property(): """ Property 2: For any search that returns results, each result should include required fields. Validates: Requirements 1.2 """ from data_manager import search_practices # Search for a term we know exists result = search_practices("async") if result.get("count", 0) > 0: for match in result["matches"]: assert "category" in match assert "topic" in match assert "description" in match assert "examples" in match assert "related_topics" in match assert isinstance(match["examples"], list) assert isinstance(match["related_topics"], list) # Property 3: Search fallback suggestions # Feature: bestpractices-mcp-server, Property 3: Search fallback suggestions def test_search_fallback_suggestions_property(): """ Property 3: For any keyword that produces no exact matches, system should return suggestions. Validates: Requirements 1.3 """ from data_manager import search_practices # Search for something that doesn't exist result = search_practices("xyznonexistent123") if result.get("count", 0) == 0: assert "suggestions" in result or "message" in result # Property 5: Category completeness # Feature: bestpractices-mcp-server, Property 5: Category completeness @settings(max_examples=20) @given(category=st.sampled_from(["general_coding", "fastapi_specific", "performance", "code_quality"])) def test_category_completeness_property(category): """ Property 5: For any valid category, requesting it should return all topics. Validates: Requirements 2.1 """ from data_manager import get_category, load_best_practices data = load_best_practices() result = get_category(category, data=data) # Get expected topics from raw data expected_topics = set(data["python_best_practices"][category].keys()) returned_topics = set(t["name"] for t in result.get("topics", [])) assert expected_topics == returned_topics # Property 7: Invalid category suggestions # Feature: bestpractices-mcp-server, Property 7: Invalid category suggestions def test_invalid_category_suggestions_property(): """ Property 7: For any invalid category name, system should suggest valid categories. Validates: Requirements 2.3 """ from data_manager import get_category result = get_category("invalid_category_xyz") assert "error" in result assert "suggestions" in result or "available_categories" in result # Property 8: Hierarchical structure preservation # Feature: bestpractices-mcp-server, Property 8: Hierarchical structure preservation def test_hierarchical_structure_preservation_property(): """ Property 8: For any category with nested topics, response should preserve structure. Validates: Requirements 2.5 """ from data_manager import get_category result = get_category("fastapi_specific") # Check structure is preserved assert "topics" in result for topic in result["topics"]: assert "name" in topic assert "description" in topic assert "examples" in topic # Property 9: Category list completeness # Feature: bestpractices-mcp-server, Property 9: Category list completeness def test_category_list_completeness_property(): """ Property 9: Category list should include all top-level categories from database. Validates: Requirements 3.1 """ from data_manager import list_all_categories, load_best_practices data = load_best_practices() result = list_all_categories(data=data) expected_categories = set(data["python_best_practices"].keys()) returned_categories = set(c["name"] for c in result["categories"]) assert expected_categories == returned_categories # Property 10: Category list structure # Feature: bestpractices-mcp-server, Property 10: Category list structure def test_category_list_structure_property(): """ Property 10: For any category in list, it should include description and topic count. Validates: Requirements 3.2, 3.3 """ from data_manager import list_all_categories result = list_all_categories() assert "categories" in result for category in result["categories"]: assert "name" in category assert "description" in category assert "topic_count" in category assert isinstance(category["topic_count"], int) assert category["topic_count"] > 0 # Property 12: Example completeness # Feature: bestpractices-mcp-server, Property 12: Example completeness def test_example_completeness_property(): """ Property 12: For any valid topic, all associated code examples should be returned. Validates: Requirements 4.1 """ from data_manager import get_examples, load_best_practices data = load_best_practices() # Test with a known topic topic = "type_hints" result = get_examples(topic, data=data) # Get expected examples from raw data for cat_data in data["python_best_practices"].values(): if topic in cat_data: expected_examples = cat_data[topic].get("examples", {}) assert result["count"] == len(expected_examples) break # Property 15: Example formatting # Feature: bestpractices-mcp-server, Property 15: Example formatting def test_example_formatting_property(): """ Property 15: For any code example, it should be formatted with syntax highlighting markers. Validates: Requirements 4.4 """ from data_manager import get_examples result = get_examples("type_hints") if "examples" in result: for example in result["examples"]: assert "code" in example # Check for markdown code block markers assert "```" in example["code"]

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/n-ochs/best-practice-mcp'

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