Skip to main content
Glama
srwlli

Documentation Generator MCP Server

by srwlli
test_planning_generator.py12.4 kB
#!/usr/bin/env python3 """ Unit tests for PlanningGenerator and create_plan tool. """ import asyncio import sys import json import tempfile import shutil from pathlib import Path # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent)) from generators.planning_generator import PlanningGenerator import tool_handlers from validation import validate_feature_name_input async def test_feature_name_validation(): """Test feature name validation.""" print("Testing feature name validation...\n") # Test 1: Valid feature names print("Test 1: Valid feature names...") valid_names = ["create-plan", "auth-system", "user_profile", "feature123", "API-v2"] for name in valid_names: try: result = validate_feature_name_input(name) assert result == name, f"Validation should return original name: {name}" except ValueError as e: raise AssertionError(f"Valid name '{name}' rejected: {e}") print(f"[PASS] All {len(valid_names)} valid names accepted\n") # Test 2: Invalid feature names (path traversal) print("Test 2: Invalid feature names (path traversal)...") invalid_names = [ "../parent", "../../etc/passwd", "feature/../secret", ".", "..", ] for name in invalid_names: try: validate_feature_name_input(name) raise AssertionError(f"Invalid name should be rejected: {name}") except ValueError: pass # Expected print(f"[PASS] All {len(invalid_names)} path traversal attempts blocked\n") # Test 3: Invalid characters print("Test 3: Invalid characters...") invalid_chars = [ "feature/name", "feature\\name", "feature name", "feature@name", "feature$name", "feature!name", ] for name in invalid_chars: try: validate_feature_name_input(name) raise AssertionError(f"Invalid character should be rejected: {name}") except ValueError: pass # Expected print(f"[PASS] All {len(invalid_chars)} invalid character names rejected\n") # Test 4: Empty/None print("Test 4: Empty/None validation...") try: validate_feature_name_input("") raise AssertionError("Empty string should be rejected") except ValueError: pass # Expected print("[PASS] Empty string rejected\n") # Test 5: Too long (>100 chars) print("Test 5: Max length validation...") long_name = "a" * 101 try: validate_feature_name_input(long_name) raise AssertionError("Name >100 chars should be rejected") except ValueError: pass # Expected print("[PASS] Long name rejected\n") print("="*60) print("[PASS] All feature name validation tests passed!") print("="*60 + "\n") return True async def test_planning_generator_basic(): """Test PlanningGenerator basic functionality.""" print("Testing PlanningGenerator basic functionality...\n") # Create temp directory for testing with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Test 1: Initialize generator print("Test 1: Initialize PlanningGenerator...") generator = PlanningGenerator(temp_path) assert generator.project_path == temp_path.resolve(), "Project path should be resolved" print("[PASS] Generator initialized\n") # Test 2: Load template (AI-optimized) print("Test 2: Load AI-optimized template...") try: template = generator.load_template() assert template is not None, "Template should not be None" assert isinstance(template, dict), "Template should be a dict" assert '_AI_INSTRUCTIONS' in template or 'REQUIRED_SECTIONS' in template, \ "Template should have expected structure" print(f"[PASS] Template loaded successfully (type: {type(template).__name__})\n") except FileNotFoundError: print("[SKIP] AI-optimized template not found (planning-template-for-ai.json)\n") # Test 3: Load context (should return None if not exists) print("Test 3: Load context (no context.json)...") context = generator.load_context("test-feature") assert context is None, "Context should be None when file doesn't exist" print("[PASS] Returns None for missing context\n") # Test 4: Create context directory and file print("Test 4: Load context (with context.json)...") feature_name = "test-feature" working_dir = temp_path / "coderef" / "working" / feature_name working_dir.mkdir(parents=True, exist_ok=True) context_file = working_dir / "context.json" test_context = { "feature_name": feature_name, "description": "Test feature", "requirements": ["req1", "req2"] } context_file.write_text(json.dumps(test_context, indent=2)) context = generator.load_context(feature_name) assert context is not None, "Context should not be None" assert context["feature_name"] == feature_name, "Context should have correct feature_name" print("[PASS] Context loaded successfully\n") # Test 5: Save plan print("Test 5: Save plan to working directory...") test_plan = { "META_DOCUMENTATION": { "feature_name": feature_name, "version": "1.0.0" }, "UNIVERSAL_PLANNING_STRUCTURE": { "0_preparation": {"status": "complete"} } } plan_path = generator.save_plan(feature_name, test_plan) assert Path(plan_path).exists(), "Plan file should exist" assert Path(plan_path).name == "plan.json", "Plan should be named plan.json" # Verify plan content saved_plan = json.loads(Path(plan_path).read_text()) assert saved_plan["META_DOCUMENTATION"]["feature_name"] == feature_name, \ "Saved plan should match original" print(f"[PASS] Plan saved to {plan_path}\n") print("="*60) print("[PASS] All PlanningGenerator basic tests passed!") print("="*60 + "\n") return True async def test_create_plan_handler(): """Test create_plan handler via MCP interface.""" print("Testing create_plan handler...\n") # Create temp directory for testing with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Test 1: Handler registration print("Test 1: Handler registration...") assert 'create_plan' in tool_handlers.TOOL_HANDLERS, \ "create_plan handler should be registered" print("[PASS] Handler registered in TOOL_HANDLERS\n") # Test 2: Invalid inputs - empty project_path print("Test 2: Invalid project_path...") result = await tool_handlers.handle_create_plan({ 'project_path': '', 'feature_name': 'test-feature' }) result_text = result[0].text assert 'error' in result_text.lower() or 'invalid' in result_text.lower(), \ "Should reject empty project_path" print("[PASS] Empty project_path rejected\n") # Test 3: Invalid inputs - invalid feature_name print("Test 3: Invalid feature_name (path traversal)...") result = await tool_handlers.handle_create_plan({ 'project_path': str(temp_path), 'feature_name': '../../../etc/passwd' }) result_text = result[0].text assert 'error' in result_text.lower() or 'invalid' in result_text.lower(), \ "Should reject path traversal in feature_name" print("[PASS] Path traversal rejected\n") # Test 4: Invalid feature_name with special characters print("Test 4: Invalid feature_name (special characters)...") result = await tool_handlers.handle_create_plan({ 'project_path': str(temp_path), 'feature_name': 'feature@name!' }) result_text = result[0].text assert 'error' in result_text.lower() or 'invalid' in result_text.lower(), \ "Should reject special characters in feature_name" print("[PASS] Special characters rejected\n") # Test 5: Valid inputs but missing template (expected to fail gracefully) print("Test 5: Valid inputs (may fail if template missing - expected)...") result = await tool_handlers.handle_create_plan({ 'project_path': str(temp_path), 'feature_name': 'test-feature' }) result_text = result[0].text # Handler should either succeed or fail with graceful error # We just verify it returns a response assert len(result_text) > 0, "Handler should return a response" if 'error' in result_text.lower(): print("[EXPECTED] Template not found or plan generation failed\n") else: print("[PASS] Handler executed successfully\n") print("="*60) print("[PASS] All create_plan handler tests passed!") print("="*60 + "\n") return True async def test_partial_plan_creation(): """Test partial plan creation with TODOs.""" print("Testing partial plan creation...\n") with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) generator = PlanningGenerator(temp_path) # Test 1: Create partial plan print("Test 1: Create partial plan with error message...") feature_name = "test-feature" error_message = "Test error: generation failed" partial_plan = generator._create_partial_plan(feature_name, error_message) assert partial_plan is not None, "Partial plan should not be None" assert 'META_DOCUMENTATION' in partial_plan, "Should have META_DOCUMENTATION" assert partial_plan['META_DOCUMENTATION']['feature_name'] == feature_name, \ "Should have correct feature_name" assert partial_plan['META_DOCUMENTATION']['status'] == 'partial', \ "Status should be 'partial'" assert 'UNIVERSAL_PLANNING_STRUCTURE' in partial_plan, "Should have planning structure" print("[PASS] Partial plan created with correct structure\n") # Test 2: Verify TODOs present print("Test 2: Verify TODOs in sections...") planning_sections = partial_plan['UNIVERSAL_PLANNING_STRUCTURE'] todo_count = 0 for section_name, section_data in planning_sections.items(): if isinstance(section_data, dict): section_str = json.dumps(section_data) if 'TODO' in section_str: todo_count += 1 assert todo_count > 0, "Partial plan should contain TODO markers" print(f"[PASS] Found {todo_count} sections with TODO markers\n") # Test 3: Save partial plan print("Test 3: Save partial plan...") plan_path = generator.save_plan(feature_name, partial_plan) assert Path(plan_path).exists(), "Partial plan should be saved" # Verify it can be loaded back saved_plan = json.loads(Path(plan_path).read_text()) assert saved_plan['META_DOCUMENTATION']['status'] == 'partial', \ "Saved plan should maintain partial status" print(f"[PASS] Partial plan saved and verified at {plan_path}\n") print("="*60) print("[PASS] All partial plan creation tests passed!") print("="*60 + "\n") return True async def main(): """Run all tests.""" print("\n" + "="*60) print("PLANNING GENERATOR TEST SUITE") print("="*60 + "\n") try: await test_feature_name_validation() await test_planning_generator_basic() await test_create_plan_handler() await test_partial_plan_creation() print("\n" + "="*60) print("ALL TESTS PASSED!") print("="*60 + "\n") return True except AssertionError as e: print("\n" + "="*60) print(f"TEST FAILED: {e}") print("="*60 + "\n") return False except Exception as e: print("\n" + "="*60) print(f"TEST ERROR: {e}") print("="*60 + "\n") import traceback traceback.print_exc() return False if __name__ == '__main__': success = asyncio.run(main()) sys.exit(0 if success else 1)

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/srwlli/docs-mcp'

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