Skip to main content
Glama
brianirish

Laravel MCP Companion

by brianirish
test_mcp_tool_functions.py21.1 kB
"""Unit tests for actual MCP tool functions exposed to AI assistants.""" from unittest.mock import patch import laravel_mcp_companion # Import the standalone functions from mcp_tools from mcp_tools import ( list_laravel_docs_impl, read_laravel_doc_content_impl, search_laravel_docs_impl, search_laravel_docs_with_context_impl, get_doc_structure_impl, browse_docs_by_category_impl ) class TestDocumentationTools: """Test documentation-related MCP tool functions.""" @patch('mcp_tools.os.listdir') @patch('mcp_tools.get_laravel_docs_metadata') def test_list_laravel_docs_specific_version(self, mock_metadata, mock_listdir, test_docs_dir): """Test listing docs for a specific version returns TOON format.""" mock_listdir.return_value = ['installation.md', 'routing.md', 'eloquent.md'] mock_metadata.return_value = { 'version': '12.x', 'sync_time': '2024-01-01T12:00:00Z', 'commit_sha': 'abc123def456' } result = list_laravel_docs_impl(test_docs_dir, "12.x") # TOON format assertions - check for data presence assert "12.x" in result assert "installation.md" in result assert "routing.md" in result assert "eloquent.md" in result assert "2024-01-01T12:00:00Z" in result @patch('mcp_tools.os.listdir') def test_list_laravel_docs_no_version_specified(self, mock_listdir, test_docs_dir): """Test listing docs when no version is specified returns TOON format.""" # Directories already created by fixture, no need to create them # Set up proper mock for os.listdir and os.path.isfile checks with patch('mcp_tools.os.path.isfile') as mock_isfile: mock_isfile.return_value = True # All files exist mock_listdir.side_effect = [ ['installation.md', 'routing.md'], # 11.x files ['installation.md', 'routing.md', 'eloquent.md'], # 12.x files ['installation.md', 'routing.md'], # 11.x files again for formatting ['installation.md', 'routing.md', 'eloquent.md'] # 12.x files again for formatting ] with patch('mcp_tools.get_laravel_docs_metadata') as mock_metadata: mock_metadata.return_value = {'sync_time': '2024-01-01T12:00:00Z', 'commit_sha': 'abc123'} result = list_laravel_docs_impl(test_docs_dir) # TOON format - check for version data assert "version" in result or "12.x" in result or "11.x" in result def test_list_laravel_docs_no_documentation(self, test_docs_dir): """Test listing docs when no documentation exists.""" empty_dir = test_docs_dir / "empty" empty_dir.mkdir() result = list_laravel_docs_impl(empty_dir, "12.x") assert "No documentation found for version 12.x" in result assert "update_laravel_docs()" in result def test_read_laravel_doc_content_success(self, test_docs_dir): """Test reading Laravel documentation content successfully.""" # Use existing version directory from fixture version_dir = test_docs_dir / "12.x" test_file = version_dir / "routing.md" test_content = "# Routing\n\nLaravel routing documentation." test_file.write_text(test_content) result = read_laravel_doc_content_impl(test_docs_dir, "routing.md", "12.x") assert result == test_content def test_read_laravel_doc_content_file_not_found(self, test_docs_dir): """Test reading non-existent documentation file.""" # Directory already exists from fixture result = read_laravel_doc_content_impl(test_docs_dir, "nonexistent.md", "12.x") assert "Documentation file not found" in result def test_read_laravel_doc_content_without_md_extension(self, test_docs_dir): """Test reading documentation file without .md extension.""" version_dir = test_docs_dir / "12.x" test_file = version_dir / "routing.md" test_content = "# Routing\n\nLaravel routing documentation." test_file.write_text(test_content) result = read_laravel_doc_content_impl(test_docs_dir, "routing", "12.x") # No .md extension assert result == test_content @patch('mcp_tools.get_file_content_cached') @patch('mcp_tools.os.listdir') def test_search_laravel_docs_success(self, mock_listdir, mock_get_content, test_docs_dir): """Test searching Laravel documentation successfully returns TOON format.""" # Directory already exists from fixture # Mock file listing mock_listdir.return_value = ['routing.md', 'eloquent.md'] # Mock file content mock_get_content.side_effect = [ "# Routing\n\nLaravel routing is awesome for web applications", "# Eloquent\n\nEloquent ORM for database routing" ] with patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']): result = search_laravel_docs_impl(test_docs_dir, "routing", "12.x") # TOON format assertions assert "routing" in result assert "12.x/routing.md" in result or "routing.md" in result assert "12.x/eloquent.md" in result or "eloquent.md" in result def test_search_laravel_docs_empty_query(self, test_docs_dir): """Test searching with empty query.""" result = search_laravel_docs_impl(test_docs_dir, "", "12.x") assert "Search query cannot be empty" in result def test_search_laravel_docs_no_results(self, test_docs_dir): """Test searching with no matches.""" # Directory already exists from fixture with patch('mcp_tools.os.listdir', return_value=['routing.md']), \ patch('mcp_tools.get_file_content_cached', return_value="# Routing\n\nBasic routing info"), \ patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']): result = search_laravel_docs_impl(test_docs_dir, "nonexistent_term", "12.x") assert "No results found for 'nonexistent_term'" in result def test_search_laravel_docs_with_context_success(self, test_docs_dir): """Test searching with context snippets.""" test_content = """# Laravel Routing Laravel routing is simple and powerful. You can define routes in your web.php file. ## Basic Routing The most basic Laravel routes accept a URI and a closure, providing a very simple and expressive method of defining routes. ```php Route::get('/', function () { return view('welcome'); }); ``` This is how routing works in Laravel applications.""" # Directory already exists from fixture with patch('mcp_tools.os.listdir', return_value=['routing.md']), \ patch('mcp_tools.get_file_content_cached', return_value=test_content), \ patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']): result = search_laravel_docs_with_context_impl(test_docs_dir, "routing", "12.x", context_length=100) assert "Search results for 'routing':" in result assert "12.x/routing.md" in result assert "**routing**" in result # Highlighted match def test_get_doc_structure_success(self, test_docs_dir): """Test getting document structure returns TOON format.""" test_content = """# Laravel Routing Laravel routing is simple and powerful. ## Basic Routing The most basic Laravel routes accept a URI and a closure. ### Route Methods Laravel provides methods for all HTTP verbs. ## Route Parameters You can capture segments of the URI within your route. """ with patch('mcp_tools.read_laravel_doc_content_impl', return_value=test_content): result = get_doc_structure_impl(test_docs_dir, "routing.md", "12.x") # TOON format assertions - check for heading data assert "routing.md" in result assert "Laravel Routing" in result assert "Basic Routing" in result assert "Route Methods" in result assert "Route Parameters" in result def test_browse_docs_by_category_frontend(self, test_docs_dir): """Test browsing docs by frontend category returns TOON format.""" version_dir = test_docs_dir / "12.x" # Create frontend-related files (version_dir / "vite.md").write_text("# Vite\n\nAsset bundling with Vite.") (version_dir / "blade.md").write_text("# Blade Templates\n\nTemplating engine.") (version_dir / "routing.md").write_text("# Routing\n\nNot frontend related.") with patch('mcp_tools.os.listdir', return_value=['vite.md', 'blade.md', 'routing.md']): result = browse_docs_by_category_impl(test_docs_dir, "frontend", "12.x") # TOON format assertions assert "frontend" in result.lower() assert "vite.md" in result assert "blade.md" in result # routing.md shouldn't be included as it doesn't match frontend keywords class TestPackageRecommendationTools: """Test package recommendation MCP tool functions.""" def test_get_laravel_package_recommendations_success(self, sample_package_catalog): """Test getting package recommendations successfully returns TOON format.""" with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog): from laravel_mcp_companion import get_laravel_package_recommendations result = get_laravel_package_recommendations("authentication for SPA") # TOON format assertions assert "authentication" in result.lower() assert "Laravel Sanctum" in result or "Sanctum" in result assert "laravel/sanctum" in result def test_get_laravel_package_recommendations_no_matches(self, sample_package_catalog): """Test getting package recommendations with no matches returns TOON error.""" with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog): from laravel_mcp_companion import get_laravel_package_recommendations result = get_laravel_package_recommendations("quantum computing") # TOON error format assert "error" in result or "No packages found" in result def test_get_laravel_package_info_success(self, sample_package_catalog): """Test getting specific package info successfully returns TOON format.""" with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog): from laravel_mcp_companion import get_laravel_package_info result = get_laravel_package_info("laravel/cashier") # TOON format assertions assert "Laravel Cashier" in result or "Cashier" in result assert "subscription" in result.lower() or "billing" in result.lower() assert "laravel/cashier" in result def test_get_laravel_package_info_not_found(self, sample_package_catalog): """Test getting info for non-existent package returns TOON error.""" with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog): from laravel_mcp_companion import get_laravel_package_info result = get_laravel_package_info("nonexistent/package") assert "error" in result or "not found" in result.lower() def test_get_laravel_package_categories_success(self, sample_package_catalog): """Test getting packages by category successfully returns TOON format.""" with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog): from laravel_mcp_companion import get_laravel_package_categories result = get_laravel_package_categories("authentication") # TOON format assertions assert "authentication" in result.lower() assert "Laravel Sanctum" in result or "Sanctum" in result assert "laravel/sanctum" in result def test_get_laravel_package_categories_no_matches(self, sample_package_catalog): """Test getting packages by category with no matches returns TOON error.""" with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog): from laravel_mcp_companion import get_laravel_package_categories result = get_laravel_package_categories("nonexistent_category") assert "error" in result or "No packages found" in result def test_get_features_for_laravel_package_success(self, sample_package_catalog): """Test getting features for a package successfully returns TOON format.""" test_features = ["spa-authentication", "api-tokens", "token-abilities"] with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog), \ patch.dict(laravel_mcp_companion.FEATURE_MAP, {'laravel/sanctum': test_features}): from laravel_mcp_companion import get_features_for_laravel_package result = get_features_for_laravel_package("laravel/sanctum") # TOON format assertions assert "laravel/sanctum" in result assert "spa-authentication" in result assert "api-tokens" in result assert "token-abilities" in result def test_get_features_for_laravel_package_not_found(self, sample_package_catalog): """Test getting features for non-existent package returns TOON error.""" with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog): from laravel_mcp_companion import get_features_for_laravel_package result = get_features_for_laravel_package("nonexistent/package") assert "error" in result or "not found" in result.lower() def test_get_features_for_laravel_package_no_features(self, sample_package_catalog): """Test getting features for package with no defined features returns TOON format.""" # Patch FEATURE_MAP to be empty for this specific package empty_feature_map = {} with patch.dict(laravel_mcp_companion.PACKAGE_CATALOG, sample_package_catalog), \ patch.dict(laravel_mcp_companion.FEATURE_MAP, empty_feature_map, clear=True): from laravel_mcp_companion import get_features_for_laravel_package result = get_features_for_laravel_package("laravel/sanctum") # TOON format - should still have package data with use_cases assert "laravel/sanctum" in result class TestMcpToolsEdgeCases: """Test edge cases and exception handlers in mcp_tools.""" def test_is_safe_path_exception_handling(self, test_docs_dir): """Test that is_safe_path handles exceptions gracefully.""" from mcp_tools import is_safe_path # Test with paths that cause exceptions (invalid paths) with patch('mcp_tools.Path.resolve') as mock_resolve: mock_resolve.side_effect = Exception("Permission denied") # Should return False when exception occurs, not raise result = is_safe_path("/some/path", "/base") assert result is False def test_get_laravel_docs_metadata_json_error(self, test_docs_dir): """Test metadata reading handles malformed JSON.""" from mcp_tools import get_laravel_docs_metadata # Create malformed metadata file metadata_dir = test_docs_dir / "12.x" / ".metadata" metadata_dir.mkdir(parents=True, exist_ok=True) (metadata_dir / "sync_info.json").write_text("{ invalid json }") # Should return empty dict on JSON error result = get_laravel_docs_metadata(test_docs_dir, "12.x") assert result == {} def test_list_laravel_docs_no_md_files(self, test_docs_dir): """Test listing docs when version exists but has no .md files.""" from mcp_tools import list_laravel_docs_impl # Create empty version dir (no .md files) empty_version = test_docs_dir / "13.x" empty_version.mkdir() result = list_laravel_docs_impl(test_docs_dir, "13.x") # Should indicate no files found assert "13.x" in result or "No documentation" in result def test_list_laravel_docs_all_versions_empty(self, test_docs_dir): """Test listing all docs when no versions have files.""" from mcp_tools import list_laravel_docs_impl # Create completely empty docs dir empty_docs = test_docs_dir.parent / "empty_docs" empty_docs.mkdir() result = list_laravel_docs_impl(empty_docs) # Should indicate no documentation found assert "No documentation files found" in result or "error" in result.lower() def test_list_laravel_docs_exception_handling(self, test_docs_dir): """Test list_laravel_docs handles exceptions gracefully.""" from mcp_tools import list_laravel_docs_impl with patch('mcp_tools.os.listdir') as mock_listdir: mock_listdir.side_effect = OSError("Permission denied") result = list_laravel_docs_impl(test_docs_dir, "12.x") # Should return error message assert "Error" in result def test_browse_docs_by_category_unknown_category(self, test_docs_dir): """Test browsing docs with unknown category.""" from mcp_tools import browse_docs_by_category_impl result = browse_docs_by_category_impl(test_docs_dir, "unknown_category", "12.x") assert "Unknown category" in result assert "available_categories" in result.lower() or "frontend" in result.lower() def test_browse_docs_by_category_version_not_found(self, test_docs_dir): """Test browsing docs when version doesn't exist.""" from mcp_tools import browse_docs_by_category_impl result = browse_docs_by_category_impl(test_docs_dir, "frontend", "99.x") assert "No documentation found for version 99.x" in result def test_browse_docs_by_category_no_matching_files(self, test_docs_dir): """Test browsing category with no matching files.""" from mcp_tools import browse_docs_by_category_impl # Create version with files that don't match any category version_dir = test_docs_dir / "12.x" # Remove existing files and create non-matching ones for f in version_dir.glob("*.md"): f.unlink() (version_dir / "random_topic.md").write_text("# Random\n\nUnrelated content.") result = browse_docs_by_category_impl(test_docs_dir, "frontend", "12.x") assert "No frontend documentation files found" in result def test_browse_docs_by_category_exception(self, test_docs_dir): """Test browse_docs_by_category handles exceptions.""" from mcp_tools import browse_docs_by_category_impl with patch('mcp_tools.os.listdir') as mock_listdir: mock_listdir.side_effect = OSError("Disk error") result = browse_docs_by_category_impl(test_docs_dir, "frontend", "12.x") assert "Error browsing documentation" in result def test_search_laravel_docs_exception_handling(self, test_docs_dir): """Test search_laravel_docs handles exceptions gracefully.""" from mcp_tools import search_laravel_docs_impl with patch('mcp_tools.os.listdir') as mock_listdir: mock_listdir.side_effect = Exception("Unexpected error") # Need to patch SUPPORTED_VERSIONS to include 12.x with patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']): result = search_laravel_docs_impl(test_docs_dir, "test", "12.x") # Should handle exception gracefully # The actual implementation might return error or empty results assert result is not None def test_search_with_context_no_results(self, test_docs_dir): """Test search with context returns appropriate message when no results.""" from mcp_tools import search_laravel_docs_with_context_impl with patch('mcp_tools.os.listdir', return_value=['test.md']), \ patch('mcp_tools.get_file_content_cached', return_value="# Test\n\nNo match here"), \ patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']): result = search_laravel_docs_with_context_impl( test_docs_dir, "nonexistent_query_xyz", "12.x" ) assert "No results found" in result def test_search_with_context_exception(self, test_docs_dir): """Test search with context handles exceptions.""" from mcp_tools import search_laravel_docs_with_context_impl with patch('mcp_tools.os.listdir') as mock_listdir: mock_listdir.side_effect = Exception("Search error") with patch('mcp_tools.SUPPORTED_VERSIONS', ['12.x']): result = search_laravel_docs_with_context_impl( test_docs_dir, "test", "12.x" ) assert "Error" in result

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/brianirish/laravel-mcp-companion'

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